Commit Graph

10 Commits

Author SHA1 Message Date
jordi fita mas 97cf117da3 Manage all media uploads in a single place
It made no sense to have a file upload in each form that needs a media,
because to reuse an existing media users would need to upload the exact
same file again; this is very unusual and unfriendly.

A better option is to have a “centralized” media section, where people
can upload files there, and then have a picker to select from there.
Ideally, there would be an upload option in the picker, but i did not
add it yet.

I’ve split the content from the media because i want users to have the
option to update a media, for instance when they need to upload a
reduced or cropped version of the same photo, without an edit they would
need to upload the file as a new media and then update all places where
the old version was used.  And i did not want to trouble people that
uploads the same photo twice: without the separate relation, doing so
would throw a constraint error.

I do not believe there is any security problem to have all companies
link their media to the same file, as they were already readable by
everyone and could upload the data from a different company to their
own; in other words, it is not worse than it was now.
2023-09-21 01:56:44 +02:00
jordi fita mas f746c82b46 Make home page’s carousel manageable via the database
I debated with myself whether to create the home_carousel relation or
rather if it would be better to have a single carousel relation for all
pages.  However, i thought that it would be actually harder to maintain
a single relation because i would need an additional column to tell one
carrousel from another, and what would that column be? An enum? A
foreign key to another relation? home_carousel carries no such issues.

I was starting to duplicate logic all over the packages, such as the
way to encode media paths or “localization” (l10n) input fields.
Therefore, i refactorized them.

In the case of media path, i added a function that accepts rows of
media, because always need the same columns from the row, and it was
yet another repetition if i needed to pass them all the time.  Plus,
these kind of functions can be called as `table.function`, that make
them look like columns from the table; if PostgreSQL implemented virtual
generated columns, i would have used that instead.

I am not sure whether that media_path function can be immutable. An
immutable function is “guaranteed to return the same results given the
same arguments forever”, which would be true if the inputs where the
hash and the original_filename columns, instead of the whole rows, but
i left it as static because i did not know whether PostgreSQL interprets
the “same row but with different values” as a different input.  That is,
whether PostgreSQL’s concept of row is the actual tuple or the space
that has a rowid, irrespective of contents; in the latter case, the
function can not be immutable.  Just to be in the safe side, i left it
stable.

The home page was starting to grow a bit too much inside the app
package, new that it has its own admin handler, and moved it all to a
separate package.
2023-09-15 01:05:38 +02:00
jordi fita mas 9a8ef8ce9f Add the language switched to the public layout
The language switcher needs the same information as languageLinks
needed, namely the list of locales and the current Path, to construct
the URI to all alternate versions.  However, in this case i need access
to this data in the template context, to build the list of links.

At first i use request’s context to hold the list of available locales
from application, and it worked, possibly without ill-effects, but i
realized that i was doing it just to avoid a new parameter.  Or, more
precise, an _explicit_ parameter; the context was used to skip the
inner functions between app and template.MustRenderPublic, but the
parameter was there all the same.

Finally, i thought that some handler might want to filter the list of
locales to show only the ones that it has a translation of.  In that
case, i would need to extract the locales from the context, filter it,
and create a new request with the updated context.  That made little
sense, and made me add the explicit locales parameter.

Since now the template has the same data as languageLinks, there is
little point of having the link in the HTTP response headers, and added
the <link> elements to <head>.

I thought that maybe i could avoid these <links> as they give the exact
same data as the language switch, but Google says nothing of using
regular anchors to gather information about localized versions of the
document[0], thus i opted to be conservative.  One can reason that the
<head> has more weight for Google, as most sites with user-generated
content, which could contain these anchors, rarely allow users to edit
the <head>.

[0]: https://developers.google.com/search/docs/specialty/international/localized-versions
2023-08-06 05:53:52 +02:00
jordi fita mas e3138652fa Rename locale.MustGetAll to GetAll and return error also
Since app.New returns error too, there is no need for locale.GetAll to
panic on error; main will log and close the application.
2023-08-06 04:08:07 +02:00
jordi fita mas ca4ecd3a3e Pass the context from app to locale.mustGetAvailableLanguages
There is no reason for that function to know the context.
2023-08-06 04:04:36 +02:00
jordi fita mas 9349cda5f6 Take into account the confidence of language.Matcher
I had a weird but that sometimes the application would use Spanish as
the default language, even though the user had not passed Spanish as
value of the Accept-Language header and the default is hard-coded to
Catalan.

I learned that language.Matcher **always** returns one of its defined
locales, even if there is no way to match the passed list of languages
to Match.  In that case, it returns a confidence of “No”, meaning that
the match failed.

Usually go the default Catalan locale because most of the time this was
set as the first language to Matcher, but since Go randomizes maps,
there were times that Spanish was first.
2023-08-05 02:08:32 +02:00
jordi fita mas f963f54839 Add profile form, inside a user menu
This is the first form that uses HTMx, and can not return a 400 error
code because otherwise HTMx does not render the content.

Since now there are pages that do not render the whole html, with header
and body, i need a different layout for these, and moved the common code
to handle forms and such a new template file that both layouts can use.
I also need the request in template.MustRender to check which layout i
should use.

Closes #7.
2023-07-26 20:46:09 +02:00
jordi fita mas 1ef6dcc4cf Get user from database based on cookie and serve login if not logged in
To get the user from the database i have to set the cookie first, that
was already done in database.MustAcquire, but i thought they were too
far apart, even thought they are so related.  So, the cookie, and thus
the role, is set when getting the user, that is actually the first thing
to do once the connection is acquired.  However, that way the database
package has no knowledge of cookies, and the code that sets the cookie
and retrieves the user are next to each other.

I applied the same logic to the changes of locale.Match: it has not
business knowing that the accept language string comes from a request;
it only needs the actual string.  Also, the TODO comment about getting
the user’s locale made no sense, now, because app already knows that
locale, so there is no need to pass the user to the locale package.

Getting the locale is done after retrieving the user from the database,
for the same reason the connection is Acquired as far up as possible:
almost every request will need this value, together with the user and
the database connection.

I am a bit affraid that i will end up with functions that always expect
these three values.  Maybe i can put the locale inside user, as it is
the user’s locale, after all, no matter if it came from the database or
the user agent, but connection and user must be separate, i think.

We’ll see.
2023-07-26 01:50:39 +02:00
jordi fita mas 7b1e17569e Add Gettext and GettextNoop to locale
xgettext does not recognize Get as marker for translatable strings, and
i can not add it as is because Go has many functions called Get, and it
would mark many strings as translatable that should not be, like HTTP
headers.

I believe that Gettext is unusual in Go, because the “correct” way to
spell that function in Go would be GetText, and would not interfere in
any other function.

GettextNoop is just a function that marks the string as translatable but
does not translate it at all.  I want this for error messages and such,
that i have to pass the string to a validator function, but it is only
necessary to translate it when the validation fails.  I doubt it makes
a difference in peformance, but still.
2023-07-24 17:09:43 +02:00
jordi fita mas 403c27f1e1 Add the skeleton of the web application
It does nothing more than to server a single page that does nothing
interesting.

This time i do not use a router.  Instead, i am trying out a technique
i have seen in an article[0] that i have tried in other, smaller,
projects and seems to work surprisingly well: it just “cuts off” the
URI path by path, passing the request from handler to handler until
it finds its way to a handler that actually serves the request.

That helps to loosen the coupling between the application and lower
handlers, and makes dependencies explicit, because i need to pass the
locale, company, etc. down instead of storing them in contexts.  Let’s
see if i do not regret it on a later date.

I also made a lot more packages that in Numerus.  In Numerus i actually
only have the single pkg package, and it works, kind of, but i notice
how i name my methods to avoid clashing instead of using packages for
that.  That is, instead of pkg.NewApp i now have app.New.

Initially i thought that Locale should be inside app, but then there was
a circular dependency between app and template.  That is why i created a
separate package, but now i am wondering if template should be inside
app too, but then i would have app.MustRenderTemplate instead of
template.MustRender.

The CSS is the most bare-bones file i could write because i am focusing
in markup right now; Oriol will fill in the file once the application is
working.

[0]: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
2023-07-23 00:11:00 +02:00