Skip to content

Commit

Permalink
editorTransfer functional.
Browse files Browse the repository at this point in the history
  • Loading branch information
bengarrett committed Jul 29, 2024
1 parent 0658b9a commit 9f101a6
Show file tree
Hide file tree
Showing 13 changed files with 339 additions and 87 deletions.
90 changes: 88 additions & 2 deletions assets/js/editor-assets.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,92 @@
// THIS FILE IS SET FOR DELETION
import { progress } from "./uploader.mjs";

(() => {
"use strict";
// THIS FILE IS SET FOR DELETION

//"use strict";

progress(`artifact-editor-dl-form`, `artifact-editor-dl-progress`);

document.body.addEventListener("htmx:beforeRequest", function (event) {
console.log(`before request`, event);
});

document.body.addEventListener("htmx:afterRequest", function (event) {
afterRequest(
event,
`artifact-editor-dl-form`,
`artifact-editor-dl-up`,
`artifact-editor-dl-feedback`
);
});

function afterRequest(event, formId, inputName, feedbackName) {
if (event.detail.elt === null) return;
if (event.detail.elt.id !== `${formId}`) return;
const input = document.getElementById(inputName);
if (input === null) {
throw new Error(`The htmx successful input element ${inputName} is null`);
}
const feedback = document.getElementById(feedbackName);
if (feedback === null) {
throw new Error(
`The htmx successful feedback element ${feedbackName} is null`
);
}
if (event.detail.successful) {
return successful(event, input, feedback);
}
if (event.detail.failed && event.detail.xhr) {
return errorXhr(event, input, feedback);
}
errorBrowser(input, feedback);
}

function successful(event, input, feedback) {
const xhr = event.detail.xhr;
feedback.innerText = `${xhr.responseText}`;
feedback.classList.remove("invalid-feedback");
feedback.classList.add("valid-feedback");
input.classList.remove("is-invalid");
input.classList.add("is-valid");
}

function errorXhr(event, input, feedback) {
const xhr = event.detail.xhr;
feedback.innerText = `Something on the server is not working, ${xhr.status} status: ${xhr.responseText}.`;
feedback.classList.remove("valid-feedback");
feedback.classList.add("invalid-feedback");
input.classList.remove("is-valid");
input.classList.add("is-invalid");
}

/**
* Displays an error message usually caused by the browser.
* @param {HTMLElement} alertElm - The alert element where the error message will be displayed.
*/
function errorBrowser(input, feedback) {
input.classList.remove("is-valid");
input.classList.add("is-invalid");
feedback.innerText =
"Something with the browser is not working, please try again or refresh the page.";
feedback.classList.remove("d-none");
}

const reset = document.getElementById(`artifact-editor-dl-reset`);
if (reset == null) {
console.error(`the reset button is missing`);
return;
}
const artifact = document.getElementById(`artifact-editor-dl-up`);
if (artifact == null) {
console.error(`the artifact file input is missing`);
return;
}
reset.addEventListener(`click`, function () {
artifact.value = ``;
artifact.classList.remove(`is-invalid`);
artifact.classList.remove(`is-valid`);
});

//alert(`editor assets script is running`);

Expand Down
1 change: 1 addition & 0 deletions docs/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- [ ] Render HTML in an iframe instead of readme? Example, http://localhost:1323/f/ad3075
- [ ] Handle magazines title on artifact page, http://localhost:1323/f/a55ed, this is hard to read, "Issue 4\nThe Pirate Syndicate +\nThe Pirate World"
- [ ] If artifact is a text file displayed in readme, then delete image preview, these are often super long, large and not needed.
- [ ] If a #hash is appended to a /f/<id> URL while signed out, then return a 404 or a redirect to the sign in page. Post signing should return to the #hash URL?

- [ ] - http://www.platohistory.org/
- [ ] - https://portcommodore.com/dokuwiki/doku.php?id=larry:comp:bbs:about_cbbs
Expand Down
10 changes: 0 additions & 10 deletions handler/app/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -972,16 +972,6 @@ func PlatformTagInfo(c echo.Context) error {
return c.String(http.StatusOK, info)
}

// PostIntro handles the POST request for the intro upload form.
func PostIntro(c echo.Context) error {
const name = "post intro"
x, err := c.FormParams()
if err != nil {
return InternalErr(c, name, err)
}
return c.JSON(http.StatusOK, x)
}

// PostDesc is the handler for the Search for file descriptions form post page.
func PostDesc(c echo.Context, input string) error {
const name = "artifacts"
Expand Down
127 changes: 122 additions & 5 deletions handler/htmx/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func transfer(c echo.Context, logger *zap.SugaredLogger, key, downloadDir string
}
id, uid, err := creator.insert(ctx, c, tx, logger)
if err != nil {
return fmt.Errorf("creator.insert: %w", err)
return c.HTML(http.StatusInternalServerError, err.Error())
} else if id == 0 {
return nil
}
Expand Down Expand Up @@ -242,6 +242,8 @@ func Duplicate(logger *zap.SugaredLogger, uid uuid.UUID, srcPath, dstDir string)
logger.Infof("Uploader copied %d bytes for %s, to the destination dir", i, uid.String())
}

// checkDest validates the destination directory for the chosen file upload,
// and confirms that the directory exists and is writable.
func checkDest(dest string) (string, error) {
st, err := os.Stat(dest)
if err != nil {
Expand Down Expand Up @@ -347,6 +349,11 @@ type creator struct {
content []string
}

var (
ErrForm = errors.New("form parameters could not be read")
ErrInsert = errors.New("form submission could not be inserted into the database")
)

func (cr creator) insert(ctx context.Context, c echo.Context, tx *sql.Tx, logger *zap.SugaredLogger,
) (int64, uuid.UUID, error) {
noID := uuid.UUID{}
Expand All @@ -355,8 +362,7 @@ func (cr creator) insert(ctx context.Context, c echo.Context, tx *sql.Tx, logger
if logger != nil {
logger.Error(err)
}
return 0, noID, c.HTML(http.StatusInternalServerError,
"The form parameters could not be read")
return 0, noID, ErrForm
}
values.Add(cr.key+"-filename", cr.file.Filename)
values.Add(cr.key+"-integrity", hex.EncodeToString(cr.checksum))
Expand All @@ -381,8 +387,7 @@ func (cr creator) insert(ctx context.Context, c echo.Context, tx *sql.Tx, logger
if logger != nil {
logger.Error(err)
}
return 0, noID, c.HTML(http.StatusInternalServerError,
"The form submission could not be inserted")
return 0, noID, ErrInsert
}
return id, uid, nil
}
Expand Down Expand Up @@ -463,3 +468,115 @@ func sanitizeID(c echo.Context, name, prod string) (int64, error) {
}
return id, nil
}

// ------------------------------------------------------------

// EditorFileUpload is a handler for the /editor/upload/file route.
func EditorFileUpload(c echo.Context, logger *zap.SugaredLogger, downloadDir string) error {
name := "editor-uploadfile" // TODO, rename
return editorTransfer(c, logger, name, downloadDir)
}

// Transfer is a generic file transfer handler that uploads and validates a chosen file upload.
// The provided name is that of the form input field. The logger is optional and if nil then
// the function will not log any debug information.
func editorTransfer(c echo.Context, logger *zap.SugaredLogger, name, downloadDir string) error {
if s, err := checkDest(downloadDir); err != nil {
return c.HTML(http.StatusInternalServerError, s)
}
unid := c.FormValue("artifact-editor-unid")
if unid == "" {
return c.String(http.StatusBadRequest,
"The editor file upload is missing the unique identifier")
}
key := c.FormValue("artifact-editor-record-key")
if key == "" {
return c.String(http.StatusBadRequest,
"The editor file upload is missing the record key")
}
id, err := strconv.ParseInt(key, 10, 64)
if err != nil {
return c.String(http.StatusBadRequest,
"The editor file upload record key is invalid")
}

file, err := c.FormFile(name)
if err != nil {
return checkFormFile(c, logger, name, err)
}
src, err := file.Open()
if err != nil {
return checkFileOpen(c, logger, name, err)
}
defer src.Close()
hasher := sha512.New384()
if _, err := io.Copy(hasher, src); err != nil {
return checkHasher(c, logger, name, err)
}
checksum := hasher.Sum(nil)
ctx := context.Background()
db, tx, err := postgres.ConnectTx()
if err != nil {
return c.HTML(http.StatusServiceUnavailable,
"Cannot begin the database transaction")
}
defer db.Close()
// exist, err := model.SHA384Exists(ctx, tx, checksum)
// if err != nil {
// return checkExist(c, logger, err)
// }
// if exist {
// return c.HTML(http.StatusOK,
// "<p>Thanks, but the chosen file already exists on Defacto2.</p>"+
// html.EscapeString(file.Filename))
// }
dst, err := copier(c, logger, file, key)
if err != nil {
return fmt.Errorf("copier: %w", err)
}
if dst == "" {
return c.HTML(http.StatusInternalServerError,
"The temporary save cannot be created")
}
content := ""
if list, err := archive.List(dst, file.Filename); err == nil {
content = strings.Join(list, "\n")
}
// readme := archive.Readme(file.Filename, content...)

fu := model.FileUpload{
Filename: file.Filename,
Integrity: hex.EncodeToString(checksum),
Content: content,
Filesize: file.Size,
}
if err := fu.Update(ctx, tx, id); err != nil {
if logger != nil {
logger.Error(err)
}
return ErrInsert // TODO: replace
}

downloadFile := filepath.Join(downloadDir, unid)
_, err = helper.DuplicateOW(dst, downloadFile)
if err != nil {
tx.Rollback()
logger.Errorf("htmx transfer duplicate file: %w,%q, %s",
err, unid, downloadFile)
return err
}
if err := tx.Commit(); err != nil {
return c.HTML(http.StatusInternalServerError, "The database commit failed")
}

// defer Duplicate(uid, dst, downloadDir)
// func Duplicate(uid uuid.UUID, srcPath, dstDir string) {
// newPath := filepath.Join(dstDir, uid.String())
// i, err := helper.Duplicate(srcPath, newPath)

// return success(c, file.Filename, id)

c.Response().Header().Set("HX-Refresh", "false")
return c.String(http.StatusOK,
fmt.Sprintf("The file %s was uploaded, about to reload the page", file.Filename))
}
12 changes: 0 additions & 12 deletions handler/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ func (c Configuration) FilesRoutes(e *echo.Echo, logger *zap.SugaredLogger, publ
e = c.custom404(e)
e = c.debugInfo(e)
e = c.static(e)
e = c.uploader(e)
e = c.html(e, public)
e = c.font(e, public)
e = c.embed(e, public)
Expand Down Expand Up @@ -300,17 +299,6 @@ func (c Configuration) search(e *echo.Echo, logger *zap.SugaredLogger) *echo.Ech
return e
}

// uploader for anonymous client uploads.
func (c Configuration) uploader(e *echo.Echo) *echo.Echo {
if e == nil {
panic(fmt.Errorf("%w for uploader router", ErrRoutes))
}
uploader := e.Group("/uploader")
uploader.Use(c.ReadOnlyLock)
uploader.GET("", app.PostIntro)
return e
}

// signin for operators.
func (c Configuration) signin(e *echo.Echo, nonce string) *echo.Echo {
if e == nil {
Expand Down
7 changes: 7 additions & 0 deletions handler/routerlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ func editor(g *echo.Group, logger *zap.SugaredLogger, dir app.Dirs) {
emu.PATCH("/umb/:id", htmx.RecordEmulateUMB)
emu.PATCH("/ems/:id", htmx.RecordEmulateEMS)
emu.PATCH("/xms/:id", htmx.RecordEmulateXMS)

// these POSTs should only be used for editor, htmx file uploads,
// and not for general file uploads or data edits.
upload := g.Group("/upload")
upload.POST("/file", func(c echo.Context) error {
return htmx.EditorFileUpload(c, logger, dir.Download)
})
}

func get(g *echo.Group, dir app.Dirs) {
Expand Down
18 changes: 16 additions & 2 deletions internal/helper/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,29 @@ func Count(dir string) (int, error) {
// Duplicate is a workaround for renaming files across different devices.
// A cross device can also be a different file system such as a Docker volume.
// It returns the number of bytes written to the new file.
// The function returns an error if the newpath already exists.
func Duplicate(oldpath, newpath string) (int64, error) {
const createNoTruncate = os.O_CREATE | os.O_WRONLY | os.O_EXCL
return duplicate(oldpath, newpath, createNoTruncate)
}

// DuplicateOW is a workaround for renaming files across different devices.
// A cross device can also be a different file system such as a Docker volume.
// It returns the number of bytes written to the new file.
// The function will truncate and overwrite the newpath if it already exists.
func DuplicateOW(oldpath, newpath string) (int64, error) {
const createTruncate = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
return duplicate(oldpath, newpath, createTruncate)
}

func duplicate(oldpath, newpath string, flag int) (int64, error) {
src, err := os.Open(oldpath)
if err != nil {
return 0, fmt.Errorf("duplicate os.open %w", err)
}
defer src.Close()

const createNoTruncate = os.O_CREATE | os.O_WRONLY | os.O_EXCL
dst, err := os.OpenFile(newpath, createNoTruncate, WriteWriteRead)
dst, err := os.OpenFile(newpath, flag, WriteWriteRead)
if err != nil {
return 0, fmt.Errorf("duplicate os.create %w", err)
}
Expand Down
Loading

0 comments on commit 9f101a6

Please sign in to comment.