2023-07-26 18:46:09 +00:00
|
|
|
/*
|
|
|
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2023-07-26 10:08:59 +00:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
2023-07-26 18:46:09 +00:00
|
|
|
"context"
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
"io"
|
2023-07-26 10:08:59 +00:00
|
|
|
"net/http"
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
2023-07-26 10:08:59 +00:00
|
|
|
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
2023-07-26 18:46:09 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/form"
|
|
|
|
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
2023-07-26 10:08:59 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/locale"
|
2023-07-26 18:46:09 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/template"
|
2023-07-26 10:08:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func (h *App) getUser(r *http.Request, conn *database.Conn) (*auth.User, error) {
|
|
|
|
cookie := auth.GetSessionCookie(r)
|
|
|
|
if _, err := conn.Exec(r.Context(), "select set_cookie($1)", cookie); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
user := &auth.User{
|
|
|
|
Email: "",
|
|
|
|
LoggedIn: false,
|
|
|
|
Role: "guest",
|
|
|
|
}
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
row := conn.QueryRow(r.Context(), "select user_id, coalesce(email, ''), email is not null, role, lang_tag, csrf_token from user_profile")
|
2023-07-26 10:08:59 +00:00
|
|
|
var langTag string
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
if err := row.Scan(&user.ID, &user.Email, &user.LoggedIn, &user.Role, &langTag, &user.CSRFToken); err != nil {
|
2023-07-26 10:08:59 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if lang, err := language.Parse(langTag); err == nil {
|
|
|
|
user.Language = lang
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
user.Locale = h.locales[user.Language]
|
|
|
|
if user.Locale == nil {
|
|
|
|
user.Locale = h.matchLocale(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *App) matchLocale(r *http.Request) *locale.Locale {
|
|
|
|
l := locale.Match(r.Header.Get("Accept-Language"), h.locales, h.languageMatcher)
|
|
|
|
if l == nil {
|
|
|
|
l = h.defaultLocale
|
|
|
|
}
|
|
|
|
return l
|
|
|
|
}
|
Add the logout button
Conceptually, to logout we have to “delete the session”, thus the best
HTTP verb would be `DELETE`. However, there is no way to send a
`DELETE` request with a regular HTML form, and it seems that never will
be[0].
I could use a POST, optionally with a “method override” technique, but
i was planing to use HTMx anyway, so this was as good an opportunity to
include it as any.
In this application i am not concerned with people not having JavaScript
enabled, because it is for a customer that has a known environment, and
we do not have much time anyway. Therefore, i opted to forgo
progressive enhancement in cases like this: if `DELETE` is needed, use
`hx-delete`.
Unfortunately, i can not use a <form> with a hidden <input> for the
CSRF token, because `DELETE` requests do not have body and the value
should be added as query parameters, like a form with GET method, but
HTMx does the incorrect thing here: sends the values in the request’s
body. That’s why i have to use a custom header and the `hx-header`
directive to include the CSRF token.
Then, by default HTMx targets the triggered element for swap with the
response from the server, but after a logout i want to redirect the
user to the login form again. I could set the hx-target to button to
replace the whole body, or tell the client to redirect to the new
location. I actually do not know which one is “better”. Maybe the
hx-target is best because then everything is handled by the client, but
in the case of logout, since it is possible that i might want to load
scripts only for logged-in users in the future, i opted for the full
page reload.
However, HTMx does not want to reload a page that return HTTP 401,
hence i had to include the GET method to /login in order to return the
login form with a response of HTTP 200, which also helps when
reloading in the browser after a failed login attempt. I am not worried
with the HTTP 401 when attempting to load a page as guest, because
this request most probably comes from the browser, not HTMx, and it will
show the login form as intended—even though it is not compliant, since
it does not return the WWW-Authenticate header, but this is the best i
can do given that no cookie-based authentication method has been
accepted[1].
[0]: https://www.w3.org/Bugs/Public/show_bug.cgi?id=10671#c16
[1]: https://datatracker.ietf.org/doc/id/draft-broyer-http-cookie-auth-00.html
2023-07-26 11:49:47 +00:00
|
|
|
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
type profileHandler struct {
|
|
|
|
fileHandle http.Handler
|
|
|
|
avatarHandle http.Handler
|
|
|
|
avatarsDir string
|
|
|
|
}
|
|
|
|
|
|
|
|
func newProfileHandler(static http.Handler, avatarsDir string) (*profileHandler, error) {
|
|
|
|
if err := os.MkdirAll(avatarsDir, 0755); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
handler := &profileHandler{
|
|
|
|
fileHandle: static,
|
|
|
|
avatarsDir: avatarsDir,
|
|
|
|
avatarHandle: http.FileServer(http.Dir(avatarsDir)),
|
|
|
|
}
|
|
|
|
return handler, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *profileHandler) Handler(user *auth.User, conn *database.Conn) http.HandlerFunc {
|
Add the logout button
Conceptually, to logout we have to “delete the session”, thus the best
HTTP verb would be `DELETE`. However, there is no way to send a
`DELETE` request with a regular HTML form, and it seems that never will
be[0].
I could use a POST, optionally with a “method override” technique, but
i was planing to use HTMx anyway, so this was as good an opportunity to
include it as any.
In this application i am not concerned with people not having JavaScript
enabled, because it is for a customer that has a known environment, and
we do not have much time anyway. Therefore, i opted to forgo
progressive enhancement in cases like this: if `DELETE` is needed, use
`hx-delete`.
Unfortunately, i can not use a <form> with a hidden <input> for the
CSRF token, because `DELETE` requests do not have body and the value
should be added as query parameters, like a form with GET method, but
HTMx does the incorrect thing here: sends the values in the request’s
body. That’s why i have to use a custom header and the `hx-header`
directive to include the CSRF token.
Then, by default HTMx targets the triggered element for swap with the
response from the server, but after a logout i want to redirect the
user to the login form again. I could set the hx-target to button to
replace the whole body, or tell the client to redirect to the new
location. I actually do not know which one is “better”. Maybe the
hx-target is best because then everything is handled by the client, but
in the case of logout, since it is possible that i might want to load
scripts only for logged-in users in the future, i opted for the full
page reload.
However, HTMx does not want to reload a page that return HTTP 401,
hence i had to include the GET method to /login in order to return the
login form with a response of HTTP 200, which also helps when
reloading in the browser after a failed login attempt. I am not worried
with the HTTP 401 when attempting to load a page as guest, because
this request most probably comes from the browser, not HTMx, and it will
show the login form as intended—even though it is not compliant, since
it does not return the WWW-Authenticate header, but this is the best i
can do given that no cookie-based authentication method has been
accepted[1].
[0]: https://www.w3.org/Bugs/Public/show_bug.cgi?id=10671#c16
[1]: https://datatracker.ietf.org/doc/id/draft-broyer-http-cookie-auth-00.html
2023-07-26 11:49:47 +00:00
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var head string
|
2023-07-31 12:22:57 +00:00
|
|
|
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
Add the logout button
Conceptually, to logout we have to “delete the session”, thus the best
HTTP verb would be `DELETE`. However, there is no way to send a
`DELETE` request with a regular HTML form, and it seems that never will
be[0].
I could use a POST, optionally with a “method override” technique, but
i was planing to use HTMx anyway, so this was as good an opportunity to
include it as any.
In this application i am not concerned with people not having JavaScript
enabled, because it is for a customer that has a known environment, and
we do not have much time anyway. Therefore, i opted to forgo
progressive enhancement in cases like this: if `DELETE` is needed, use
`hx-delete`.
Unfortunately, i can not use a <form> with a hidden <input> for the
CSRF token, because `DELETE` requests do not have body and the value
should be added as query parameters, like a form with GET method, but
HTMx does the incorrect thing here: sends the values in the request’s
body. That’s why i have to use a custom header and the `hx-header`
directive to include the CSRF token.
Then, by default HTMx targets the triggered element for swap with the
response from the server, but after a logout i want to redirect the
user to the login form again. I could set the hx-target to button to
replace the whole body, or tell the client to redirect to the new
location. I actually do not know which one is “better”. Maybe the
hx-target is best because then everything is handled by the client, but
in the case of logout, since it is possible that i might want to load
scripts only for logged-in users in the future, i opted for the full
page reload.
However, HTMx does not want to reload a page that return HTTP 401,
hence i had to include the GET method to /login in order to return the
login form with a response of HTTP 200, which also helps when
reloading in the browser after a failed login attempt. I am not worried
with the HTTP 401 when attempting to load a page as guest, because
this request most probably comes from the browser, not HTMx, and it will
show the login form as intended—even though it is not compliant, since
it does not return the WWW-Authenticate header, but this is the best i
can do given that no cookie-based authentication method has been
accepted[1].
[0]: https://www.w3.org/Bugs/Public/show_bug.cgi?id=10671#c16
[1]: https://datatracker.ietf.org/doc/id/draft-broyer-http-cookie-auth-00.html
2023-07-26 11:49:47 +00:00
|
|
|
|
|
|
|
switch head {
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
case "avatar":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
|
|
|
h.serveAvatar(w, r, user)
|
|
|
|
default:
|
|
|
|
methodNotAllowed(w, r, http.MethodGet)
|
|
|
|
}
|
Add the logout button
Conceptually, to logout we have to “delete the session”, thus the best
HTTP verb would be `DELETE`. However, there is no way to send a
`DELETE` request with a regular HTML form, and it seems that never will
be[0].
I could use a POST, optionally with a “method override” technique, but
i was planing to use HTMx anyway, so this was as good an opportunity to
include it as any.
In this application i am not concerned with people not having JavaScript
enabled, because it is for a customer that has a known environment, and
we do not have much time anyway. Therefore, i opted to forgo
progressive enhancement in cases like this: if `DELETE` is needed, use
`hx-delete`.
Unfortunately, i can not use a <form> with a hidden <input> for the
CSRF token, because `DELETE` requests do not have body and the value
should be added as query parameters, like a form with GET method, but
HTMx does the incorrect thing here: sends the values in the request’s
body. That’s why i have to use a custom header and the `hx-header`
directive to include the CSRF token.
Then, by default HTMx targets the triggered element for swap with the
response from the server, but after a logout i want to redirect the
user to the login form again. I could set the hx-target to button to
replace the whole body, or tell the client to redirect to the new
location. I actually do not know which one is “better”. Maybe the
hx-target is best because then everything is handled by the client, but
in the case of logout, since it is possible that i might want to load
scripts only for logged-in users in the future, i opted for the full
page reload.
However, HTMx does not want to reload a page that return HTTP 401,
hence i had to include the GET method to /login in order to return the
login form with a response of HTTP 200, which also helps when
reloading in the browser after a failed login attempt. I am not worried
with the HTTP 401 when attempting to load a page as guest, because
this request most probably comes from the browser, not HTMx, and it will
show the login form as intended—even though it is not compliant, since
it does not return the WWW-Authenticate header, but this is the best i
can do given that no cookie-based authentication method has been
accepted[1].
[0]: https://www.w3.org/Bugs/Public/show_bug.cgi?id=10671#c16
[1]: https://datatracker.ietf.org/doc/id/draft-broyer-http-cookie-auth-00.html
2023-07-26 11:49:47 +00:00
|
|
|
case "session":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodDelete:
|
|
|
|
handleLogout(w, r, user, conn)
|
|
|
|
default:
|
|
|
|
methodNotAllowed(w, r, http.MethodDelete)
|
|
|
|
}
|
2023-07-26 18:46:09 +00:00
|
|
|
case "":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
|
|
|
serveProfileForm(w, r, user, conn)
|
|
|
|
case http.MethodPut:
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
h.updateProfile(w, r, user, conn)
|
2023-07-26 18:46:09 +00:00
|
|
|
default:
|
|
|
|
methodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
|
|
|
}
|
Add the logout button
Conceptually, to logout we have to “delete the session”, thus the best
HTTP verb would be `DELETE`. However, there is no way to send a
`DELETE` request with a regular HTML form, and it seems that never will
be[0].
I could use a POST, optionally with a “method override” technique, but
i was planing to use HTMx anyway, so this was as good an opportunity to
include it as any.
In this application i am not concerned with people not having JavaScript
enabled, because it is for a customer that has a known environment, and
we do not have much time anyway. Therefore, i opted to forgo
progressive enhancement in cases like this: if `DELETE` is needed, use
`hx-delete`.
Unfortunately, i can not use a <form> with a hidden <input> for the
CSRF token, because `DELETE` requests do not have body and the value
should be added as query parameters, like a form with GET method, but
HTMx does the incorrect thing here: sends the values in the request’s
body. That’s why i have to use a custom header and the `hx-header`
directive to include the CSRF token.
Then, by default HTMx targets the triggered element for swap with the
response from the server, but after a logout i want to redirect the
user to the login form again. I could set the hx-target to button to
replace the whole body, or tell the client to redirect to the new
location. I actually do not know which one is “better”. Maybe the
hx-target is best because then everything is handled by the client, but
in the case of logout, since it is possible that i might want to load
scripts only for logged-in users in the future, i opted for the full
page reload.
However, HTMx does not want to reload a page that return HTTP 401,
hence i had to include the GET method to /login in order to return the
login form with a response of HTTP 200, which also helps when
reloading in the browser after a failed login attempt. I am not worried
with the HTTP 401 when attempting to load a page as guest, because
this request most probably comes from the browser, not HTMx, and it will
show the login form as intended—even though it is not compliant, since
it does not return the WWW-Authenticate header, but this is the best i
can do given that no cookie-based authentication method has been
accepted[1].
[0]: https://www.w3.org/Bugs/Public/show_bug.cgi?id=10671#c16
[1]: https://datatracker.ietf.org/doc/id/draft-broyer-http-cookie-auth-00.html
2023-07-26 11:49:47 +00:00
|
|
|
default:
|
|
|
|
http.NotFound(w, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-26 18:46:09 +00:00
|
|
|
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
func (h *profileHandler) serveAvatar(w http.ResponseWriter, r *http.Request, user *auth.User) {
|
|
|
|
avatarPath := h.avatarPath(user)
|
|
|
|
if _, err := os.Stat(avatarPath); err == nil {
|
|
|
|
r.URL.Path = path.Base(avatarPath)
|
|
|
|
h.avatarHandle.ServeHTTP(w, r)
|
|
|
|
} else {
|
|
|
|
r.URL.Path = "default_avatar.svg"
|
|
|
|
h.fileHandle.ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *profileHandler) avatarPath(user *auth.User) string {
|
|
|
|
return filepath.Join(h.avatarsDir, strconv.Itoa(user.ID))
|
|
|
|
}
|
|
|
|
|
2023-07-26 18:46:09 +00:00
|
|
|
func serveProfileForm(w http.ResponseWriter, r *http.Request, user *auth.User, conn *database.Conn) {
|
|
|
|
profile := newProfileForm(r.Context(), user.Locale, conn)
|
|
|
|
profile.Name.Val = conn.MustGetText(r.Context(), "select name from user_profile")
|
|
|
|
profile.Email.Val = user.Email
|
|
|
|
profile.Language.Selected = []string{user.Language.String()}
|
|
|
|
profile.MustRender(w, r, user)
|
|
|
|
}
|
|
|
|
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
func (h *profileHandler) updateProfile(w http.ResponseWriter, r *http.Request, user *auth.User, conn *database.Conn) {
|
2023-07-26 18:46:09 +00:00
|
|
|
profile := newProfileForm(r.Context(), user.Locale, conn)
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
if err := profile.Parse(w, r); err != nil {
|
2023-07-26 18:46:09 +00:00
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
defer profile.Close()
|
2023-07-26 18:46:09 +00:00
|
|
|
if err := user.VerifyCSRFToken(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !profile.Valid(user.Locale) {
|
|
|
|
if !httplib.IsHTMxRequest(r) {
|
|
|
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
|
|
}
|
|
|
|
profile.MustRender(w, r, user)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
//goland:noinspection SqlWithoutWhere
|
|
|
|
cookie := conn.MustGetText(r.Context(), "update user_profile set name = $1, email = $2, lang_tag = $3 returning build_cookie()", profile.Name, profile.Email, profile.Language)
|
|
|
|
auth.SetSessionCookie(w, cookie)
|
|
|
|
if profile.Password.Val != "" {
|
|
|
|
conn.MustExec(r.Context(), "select change_password($1)", profile.Password)
|
|
|
|
}
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
redirect := user.Language.String() != profile.Language.String()
|
|
|
|
if profile.HasAvatarFile() {
|
|
|
|
redirect = true
|
|
|
|
if err := profile.SaveAvatarTo(h.avatarPath(user)); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if redirect {
|
2023-07-26 18:46:09 +00:00
|
|
|
httplib.Redirect(w, r, "/me", http.StatusSeeOther)
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
} else {
|
|
|
|
httplib.Relocate(w, r, "/me", http.StatusSeeOther)
|
2023-07-26 18:46:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type profileForm struct {
|
|
|
|
Name *form.Input
|
|
|
|
Email *form.Input
|
|
|
|
Password *form.Input
|
|
|
|
PasswordConfirm *form.Input
|
|
|
|
Language *form.Select
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
Avatar *form.File
|
2023-07-26 18:46:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newProfileForm(ctx context.Context, l *locale.Locale, conn *database.Conn) *profileForm {
|
|
|
|
automaticOption := l.Pgettext("Automatic", "language option")
|
|
|
|
languages := form.MustGetOptions(ctx, conn, "select 'und', $1 union all select lang_tag, endonym from language where selectable", automaticOption)
|
|
|
|
return &profileForm{
|
|
|
|
Name: &form.Input{
|
|
|
|
Name: "name",
|
|
|
|
},
|
|
|
|
Email: &form.Input{
|
|
|
|
Name: "email",
|
|
|
|
},
|
|
|
|
Password: &form.Input{
|
|
|
|
Name: "password",
|
|
|
|
},
|
|
|
|
PasswordConfirm: &form.Input{
|
|
|
|
Name: "password_confirm",
|
|
|
|
},
|
|
|
|
Language: &form.Select{
|
|
|
|
Name: "language",
|
|
|
|
Options: languages,
|
|
|
|
},
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
Avatar: &form.File{
|
|
|
|
Name: "avatar",
|
|
|
|
MaxSize: 1 << 20,
|
|
|
|
},
|
2023-07-26 18:46:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
func (f *profileForm) Parse(w http.ResponseWriter, r *http.Request) error {
|
|
|
|
maxSize := f.Avatar.MaxSize + 1024
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxSize)
|
|
|
|
if err := r.ParseMultipartForm(maxSize); err != nil {
|
2023-07-26 18:46:09 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
f.Email.FillValue(r)
|
|
|
|
f.Name.FillValue(r)
|
|
|
|
f.Password.FillValue(r)
|
|
|
|
f.PasswordConfirm.FillValue(r)
|
|
|
|
f.Language.FillValue(r)
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
if err := f.Avatar.FillValue(r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-07-26 18:46:09 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
func (f *profileForm) Close() error {
|
|
|
|
return f.Avatar.Close()
|
|
|
|
}
|
|
|
|
|
2023-07-26 18:46:09 +00:00
|
|
|
func (f *profileForm) Valid(l *locale.Locale) bool {
|
|
|
|
v := form.NewValidator(l)
|
|
|
|
if v.CheckRequired(f.Email, l.GettextNoop("Email can not be empty.")) {
|
|
|
|
v.CheckValidEmail(f.Email, l.GettextNoop("This email is not valid. It should be like name@domain.com."))
|
|
|
|
}
|
|
|
|
v.CheckRequired(f.Name, l.GettextNoop("Name can not be empty."))
|
|
|
|
v.CheckPasswordConfirmation(f.Password, f.PasswordConfirm, l.GettextNoop("Confirmation does not match password."))
|
|
|
|
v.CheckSelectedOptions(f.Language, l.GettextNoop("Selected language is not valid."))
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
if f.HasAvatarFile() {
|
|
|
|
v.CheckImageFile(f.Avatar, l.GettextNoop("File must be a valid PNG or JPEG image."))
|
|
|
|
}
|
2023-07-26 18:46:09 +00:00
|
|
|
return v.AllOK
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *profileForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User) {
|
|
|
|
template.MustRender(w, r, user, "profile.gohtml", f)
|
|
|
|
}
|
Allow users to update their profile images
I do not see the profile image as an “integral part” of the user (i.e.,
no need for constraints), hence i do not want to store it in the
database, as would do for the identification image during check-in.
By default, i store the avatars in /var/lib/camper/avatars, but it is a
variable to allow packagers change this value using the linker.
This is also served as a test bed for uploading files to the server,
that now has a better interface and uses less resources that what i did
to Numerus.
Now the profile handler needs to keep a variable to know the path to the
avatars’ directory, thus i had to change it to a struct nested in app,
much like the fileHandler does. It still has to return the HandlerFunc,
however, as this function needs to close over the user and connection
variables.
Part of #7.
2023-07-28 18:15:09 +00:00
|
|
|
|
|
|
|
func (f *profileForm) HasAvatarFile() bool {
|
|
|
|
return f.Avatar.HasData()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *profileForm) SaveAvatarTo(avatarPath string) error {
|
|
|
|
if !f.HasAvatarFile() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
avatar, err := os.OpenFile(avatarPath, os.O_WRONLY|os.O_CREATE, 0644)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer avatar.Close()
|
|
|
|
_, err = io.Copy(avatar, f.Avatar)
|
|
|
|
return err
|
|
|
|
}
|