Skip to content

Commit

Permalink
Modified user create, edit and update routes to use right http verbs
Browse files Browse the repository at this point in the history
  • Loading branch information
svera authored Jul 15, 2024
1 parent cb2cdd9 commit 3deae20
Show file tree
Hide file tree
Showing 17 changed files with 263 additions and 85 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
#branches: [ main ]

jobs:

Expand All @@ -23,4 +23,4 @@ jobs:

- name: Test
run: go test -v ./... --cover

10 changes: 5 additions & 5 deletions internal/webserver/controller/user/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

// Update gathers information from the edit user form and updates user data
func (u *Controller) Update(c *fiber.Ctx) error {
user, err := u.repository.FindByUuid(c.FormValue("id"))
user, err := u.repository.FindByUsername(c.Params("username"))
if err != nil {
log.Println(err.Error())
return fiber.ErrInternalServerError
Expand All @@ -27,7 +27,7 @@ func (u *Controller) Update(c *fiber.Ctx) error {
session = val
}

if session.Role != model.RoleAdmin && user.Uuid != session.Uuid {
if session.Role != model.RoleAdmin && user.Username != session.Username {
return fiber.ErrForbidden
}

Expand All @@ -51,13 +51,13 @@ func (u *Controller) updateUserData(c *fiber.Ctx, user *model.User, session mode
}

if len(validationErrs) > 0 {
return c.Render("users/edit", fiber.Map{
return c.Status(fiber.StatusBadRequest).Render("users/edit", fiber.Map{
"Title": "Edit user",
"User": user,
"MinPasswordLength": u.config.MinPasswordLength,
"UsernamePattern": model.UsernamePattern,
"Errors": validationErrs,
}, "layout")
}, "partials/main")
}

if err := u.repository.Update(user); err != nil {
Expand Down Expand Up @@ -89,7 +89,7 @@ func (u *Controller) updateUserData(c *fiber.Ctx, user *model.User, session mode
"UsernamePattern": model.UsernamePattern,
"Errors": validationErrs,
"Message": "Profile updated",
}, "layout")
}, "partials/main")
}

func (u *Controller) validate(c *fiber.Ctx, user *model.User, session model.Session) (map[string]string, error) {
Expand Down
2 changes: 1 addition & 1 deletion internal/webserver/embedded/js/foliate-js/reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ const url = document.getElementById('url').value
if (url) fetch(url)
.then(res => {
if (res.status == 403) {
location.reload()
return location.reload()
}
return res.blob()
})
Expand Down
1 change: 1 addition & 0 deletions internal/webserver/embedded/js/htmx.min.js

Large diffs are not rendered by default.

129 changes: 129 additions & 0 deletions internal/webserver/embedded/js/response-targets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
(function() {
/** @type {import("../htmx").HtmxInternalApi} */
var api

var attrPrefix = 'hx-target-'

// IE11 doesn't support string.startsWith
function startsWith(str, prefix) {
return str.substring(0, prefix.length) === prefix
}

/**
* @param {HTMLElement} elt
* @param {number} respCode
* @returns {HTMLElement | null}
*/
function getRespCodeTarget(elt, respCodeNumber) {
if (!elt || !respCodeNumber) return null

var respCode = respCodeNumber.toString()

// '*' is the original syntax, as the obvious character for a wildcard.
// The 'x' alternative was added for maximum compatibility with HTML
// templating engines, due to ambiguity around which characters are
// supported in HTML attributes.
//
// Start with the most specific possible attribute and generalize from
// there.
var attrPossibilities = [
respCode,

respCode.substr(0, 2) + '*',
respCode.substr(0, 2) + 'x',

respCode.substr(0, 1) + '*',
respCode.substr(0, 1) + 'x',
respCode.substr(0, 1) + '**',
respCode.substr(0, 1) + 'xx',

'*',
'x',
'***',
'xxx'
]
if (startsWith(respCode, '4') || startsWith(respCode, '5')) {
attrPossibilities.push('error')
}

for (var i = 0; i < attrPossibilities.length; i++) {
var attr = attrPrefix + attrPossibilities[i]
var attrValue = api.getClosestAttributeValue(elt, attr)
if (attrValue) {
if (attrValue === 'this') {
return api.findThisElement(elt, attr)
} else {
return api.querySelectorExt(elt, attrValue)
}
}
}

return null
}

/** @param {Event} evt */
function handleErrorFlag(evt) {
if (evt.detail.isError) {
if (htmx.config.responseTargetUnsetsError) {
evt.detail.isError = false
}
} else if (htmx.config.responseTargetSetsError) {
evt.detail.isError = true
}
}

htmx.defineExtension('response-targets', {

/** @param {import("../htmx").HtmxInternalApi} apiRef */
init: function(apiRef) {
api = apiRef

if (htmx.config.responseTargetUnsetsError === undefined) {
htmx.config.responseTargetUnsetsError = true
}
if (htmx.config.responseTargetSetsError === undefined) {
htmx.config.responseTargetSetsError = false
}
if (htmx.config.responseTargetPrefersExisting === undefined) {
htmx.config.responseTargetPrefersExisting = false
}
if (htmx.config.responseTargetPrefersRetargetHeader === undefined) {
htmx.config.responseTargetPrefersRetargetHeader = true
}
},

/**
* @param {string} name
* @param {Event} evt
*/
onEvent: function(name, evt) {
if (name === 'htmx:beforeSwap' &&
evt.detail.xhr &&
evt.detail.xhr.status !== 200) {
if (evt.detail.target) {
if (htmx.config.responseTargetPrefersExisting) {
evt.detail.shouldSwap = true
handleErrorFlag(evt)
return true
}
if (htmx.config.responseTargetPrefersRetargetHeader &&
evt.detail.xhr.getAllResponseHeaders().match(/HX-Retarget:/i)) {
evt.detail.shouldSwap = true
handleErrorFlag(evt)
return true
}
}
if (!evt.detail.requestConfig) {
return true
}
var target = getRespCodeTarget(evt.detail.requestConfig.elt, evt.detail.xhr.status)
if (target) {
handleErrorFlag(evt)
evt.detail.shouldSwap = true
evt.detail.target = target
}
return true
}
}
})
})()
25 changes: 25 additions & 0 deletions internal/webserver/embedded/js/xh-error-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
document.body.addEventListener('htmx:afterRequest', function (evt) {
const errorTarget = document.getElementById("box-error")
const unexpectedServerError = errorTarget.getAttribute("data-unexpected-server-error")
const unexpectedError = errorTarget.getAttribute("data-unexpected-error")
if (evt.detail.successful) {
// Successful request, clear out alert
errorTarget.setAttribute("hidden", "true")
errorTarget.innerText = "";
} else if (evt.detail.failed && evt.detail.xhr) {
// Server error with response contents, equivalent to htmx:responseError
const xhr = evt.detail.xhr;
if (xhr.status == "403") {
return location.reload()
}

console.warn("Server error", evt.detail)
errorTarget.innerText = unexpectedServerError + `${xhr.status} - ${xhr.statusText}`
errorTarget.removeAttribute("hidden")
} else {
// Unspecified failure, usually caused by network error
console.error("Unexpected htmx error", evt.detail)
errorTarget.innerText = unexpectedError
errorTarget.removeAttribute("hidden")
}
});
4 changes: 3 additions & 1 deletion internal/webserver/embedded/translations/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
"Indexing in progress, search results may not be accurate.": "Indexando documentos, los resultados de búsqueda pueden no ser precisos."
"Remaining time: %s minutes": "Tiempo restante: %s minutos"
"There was an error deleting the user, please try again later": "Hubo un error al borrar el usuario, por favor, vuelva a intentarlo más tarde"
"A user with this username already exists": "Ya exista un usuario con ese nombre de usuario"
"A user with this username already exists": "Ya existe un usuario con ese nombre de usuario"
"A user with this email address already exists": "Ya existe un usuario con esa dirección de correo electrónico"
"Username can only have letters, numbers, _, - and .": "El nombre de usuario solo puede contener letras, números. _, - y ."
"Only letters, numbers, _, - and . allowed": "Solo se permiten letras, números, _, - y ."
Expand All @@ -133,3 +133,5 @@
"Go left": "Ir a la izquierda"
"Go right": "Ir a la derecha"
"Session expired, please log in again.": "Sesión expirada, por favor identifícate de nuevo."
"Unexpected error, check your connection and try to refresh the page.": "Error inesperado, comprueba tu conexión y recarga la página."
"Unexpected server error": "Error inesperado en el servidor"
2 changes: 2 additions & 0 deletions internal/webserver/embedded/translations/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,5 @@
"Go left": "Aller à gauche"
"Go right": "Aller à droite"
"Session expired, please log in again.": "Session expirée, veuillez vous reconnecter."
"Unexpected error, check your connection and try to refresh the page.": "Erreur inattendue, vérifiez votre connexion et essayez d'actualiser la page."
"Unexpected server error": "Erreur de serveur inattendue"
55 changes: 12 additions & 43 deletions internal/webserver/embedded/views/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<script src="/js/color-mode-toggler.js"></script>
</head>

<body class="d-flex flex-column h-100">
<body class="d-flex flex-column h-100" hx-ext="response-targets">
<header>
<nav class="navbar navbar-expand-md fixed-top">
<div class="container">
Expand Down Expand Up @@ -62,7 +62,7 @@ <h5 class="offcanvas-title" id="offcanvasNavbarLabel">Coreander</h5>
{{t .Lang "Users"}}
</a>
{{else}}
<a class="dropdown-item" href="/{{.Lang}}/users/{{.Session.Username}}/edit">
<a class="dropdown-item" href="/{{.Lang}}/users/{{.Session.Username}}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-person-fill" viewBox="0 0 16 16">
<path
Expand Down Expand Up @@ -130,49 +130,16 @@ <h5 class="offcanvas-title" id="offcanvasNavbarLabel">Coreander</h5>
</div>
</nav>
</header>

<main class="mt-5">
<div class="container mt-5">
{{if .RemainingIndexingTime}}
<div class="row text-start">
<div class="alert alert-warning" role="alert">
<p>{{t .Lang "Indexing in progress, search results may not be accurate."}}</p>
<div class="progress" role="progressbar" aria-label='{{t .Lang "Indexing progress"}}' aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: {{.IndexingProgressPercentage}}%">{{.IndexingProgressPercentage}}%</div>
</div>
<p class="text-end"><small>{{t .Lang "Remaining time: %s minutes" .RemainingIndexingTime}}</small></p>
</div>
</div>
{{end}}

{{if .Error}}
<div class="row text-start">
<div class="alert alert-danger" role="alert">
{{t .Lang .Error}}
</div>
</div>
{{end}}

{{if .Warning}}
<div class="row text-start">
<div class="alert alert-warning" role="alert">
{{t .Lang .Warning}}
</div>
</div>
{{end}}

{{if .Message}}
<div class="row text-start">
<div class="alert alert-success" role="alert">
{{t .Lang .Message}}
</div>
</div>
{{end}}

{{embed}}
</div>
{{template "partials/main"
dict "Lang" .Lang
"RemainingIndexingTime" .RemainingIndexingTime
"IndexingProgressPercentage" .IndexingProgressPercentage
"Error" .Error
"Warning" .Warning
"Message" .Message
"Embed" .Embed}}
</main>

<footer class="footer mt-auto py-5">
<div class="container">
<span class="text-muted"><small>{{t .Lang "Made with words by Sergio Vera"}} - <a
Expand All @@ -181,6 +148,8 @@ <h5 class="offcanvas-title" id="offcanvasNavbarLabel">Coreander</h5>
</div>
</footer>
<script src="/js/bootstrap.bundle.min.js"></script>
<script src="/js/htmx.min.js"></script>
<script src="/js/response-targets.js"></script>
</body>

</html>
44 changes: 44 additions & 0 deletions internal/webserver/embedded/views/partials/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<div class="container mt-5">
{{if .RemainingIndexingTime}}
<div class="row text-start">
<div class="alert alert-warning" role="alert">
<p>{{t .Lang "Indexing in progress, search results may not be accurate."}}</p>
<div class="progress" role="progressbar" aria-label='{{t .Lang "Indexing progress"}}' aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: {{.IndexingProgressPercentage}}%">{{.IndexingProgressPercentage}}%</div>
</div>
<p class="text-end"><small>{{t .Lang "Remaining time: %s minutes" .RemainingIndexingTime}}</small></p>
</div>
</div>
{{end}}

<div class="row text-start">
<div class="alert alert-danger" role="alert" hidden id="box-error" data-unexpected-error='{{t .Lang "Unexpected error, check your connection and try to refresh the page."}}' data-unexpected-server-error='{{t .Lang "Unexpected server error"}}'>
</div>
</div>

{{if .Error}}
<div class="row text-start">
<div class="alert alert-danger" role="alert">
{{t .Lang .Error}}
</div>
</div>
{{end}}

{{if .Warning}}
<div class="row text-start">
<div class="alert alert-warning" role="alert">
{{t .Lang .Warning}}
</div>
</div>
{{end}}

{{if .Message}}
<div class="row text-start">
<div class="alert alert-success" role="alert">
{{t .Lang .Message}}
</div>
</div>
{{end}}

{{embed}}
</div>
Loading

0 comments on commit 3deae20

Please sign in to comment.