Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
/*
|
|
|
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
package services
|
|
|
|
|
|
|
|
import (
|
2023-09-25 18:10:33 +00:00
|
|
|
"context"
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
"net/http"
|
2023-09-25 18:10:33 +00:00
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/jackc/pgx/v4"
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
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-20 23:56:44 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/carousel"
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
2023-09-25 18:10:33 +00:00
|
|
|
"dev.tandem.ws/tandem/camper/pkg/form"
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/locale"
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/template"
|
|
|
|
)
|
|
|
|
|
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-20 23:56:44 +00:00
|
|
|
const carouselName = "services"
|
|
|
|
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
type AdminHandler struct {
|
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-20 23:56:44 +00:00
|
|
|
locales locale.Locales
|
|
|
|
carousel *carousel.AdminHandler
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewAdminHandler(locales locale.Locales) *AdminHandler {
|
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-20 23:56:44 +00:00
|
|
|
return &AdminHandler{
|
|
|
|
locales: locales,
|
2023-12-20 18:52:14 +00:00
|
|
|
carousel: carousel.NewAdminHandler(carouselName, serveServicesIndex, locales),
|
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-20 23:56:44 +00:00
|
|
|
}
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var head string
|
|
|
|
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
|
|
|
|
|
|
|
switch head {
|
|
|
|
case "":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
2023-12-20 18:52:14 +00:00
|
|
|
serveServicesIndex(w, r, user, company, conn)
|
2023-09-26 14:51:35 +00:00
|
|
|
case http.MethodPost:
|
|
|
|
addService(w, r, user, company, conn)
|
|
|
|
default:
|
|
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
|
|
|
|
}
|
|
|
|
case "new":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
2024-01-12 20:06:12 +00:00
|
|
|
f := newServiceForm(r.Context(), conn, company)
|
2023-09-26 14:51:35 +00:00
|
|
|
f.MustRender(w, r, user, company)
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
default:
|
|
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet)
|
|
|
|
}
|
2024-01-13 00:15:24 +00:00
|
|
|
case "order":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodPost:
|
|
|
|
orderServices(w, r, user, company, conn)
|
|
|
|
default:
|
|
|
|
httplib.MethodNotAllowed(w, r, http.MethodPost)
|
|
|
|
}
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
case "slides":
|
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-20 23:56:44 +00:00
|
|
|
h.carousel.Handler(user, company, conn).ServeHTTP(w, r)
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
default:
|
2023-09-25 18:10:33 +00:00
|
|
|
id, err := strconv.Atoi(head)
|
|
|
|
if err != nil {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
2024-01-12 20:06:12 +00:00
|
|
|
f := newServiceForm(r.Context(), conn, company)
|
2023-09-25 18:10:33 +00:00
|
|
|
if err := f.FillFromDatabase(r.Context(), conn, id); err != nil {
|
|
|
|
if database.ErrorIsNotFound(err) {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-01-12 20:06:12 +00:00
|
|
|
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
|
|
|
switch head {
|
2023-09-25 18:10:33 +00:00
|
|
|
case "":
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
|
|
|
f.MustRender(w, r, user, company)
|
|
|
|
case http.MethodPut:
|
|
|
|
editService(w, r, user, company, conn, f)
|
2023-09-26 15:29:49 +00:00
|
|
|
case http.MethodDelete:
|
|
|
|
deleteService(w, r, user, conn, id)
|
2023-09-25 18:10:33 +00:00
|
|
|
default:
|
2023-09-26 15:29:49 +00:00
|
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut, http.MethodDelete)
|
2023-09-25 18:10:33 +00:00
|
|
|
}
|
|
|
|
default:
|
2024-01-12 20:06:12 +00:00
|
|
|
http.NotFound(w, r)
|
2023-09-25 18:10:33 +00:00
|
|
|
}
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-13 00:15:24 +00:00
|
|
|
func orderServices(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := user.VerifyCSRFToken(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
input := r.PostForm["service_id"]
|
|
|
|
if len(input) > 0 {
|
|
|
|
var ids []int
|
|
|
|
for _, s := range input {
|
|
|
|
if id, err := strconv.Atoi(s); err == nil {
|
|
|
|
ids = append(ids, id)
|
|
|
|
} else {
|
|
|
|
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := conn.OrderServices(r.Context(), ids); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
serveServicesIndex(w, r, user, company, conn)
|
|
|
|
}
|
|
|
|
|
2023-12-20 18:52:14 +00:00
|
|
|
func serveServicesIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
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-20 23:56:44 +00:00
|
|
|
slides, err := carousel.CollectSlideEntries(r.Context(), company, conn, carouselName)
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2023-09-25 18:10:33 +00:00
|
|
|
services, err := collectServiceEntries(r.Context(), company, conn)
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
page := &servicesIndex{
|
2023-09-25 18:10:33 +00:00
|
|
|
Services: services,
|
|
|
|
Slides: slides,
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
}
|
|
|
|
page.MustRender(w, r, user, company)
|
|
|
|
}
|
|
|
|
|
|
|
|
type servicesIndex struct {
|
2023-09-25 18:10:33 +00:00
|
|
|
Services []*serviceEntry
|
|
|
|
Slides []*carousel.SlideEntry
|
Add the services page
This page is more or less similar to home, in terms of database: it
has a carousel and a list of items; in this case, the definition of
campsite services.
As i said early, when adding the home carousel, this carousel has its
own relation and set of functions to manage slides. They are also
duplicated in Go code, but i think i will need to refactor it later to
a carousel package or something like that, because both relations have
the exact same fields and types, so it makes no sense to have twice the
same code.
I already did it with the CSS and JavaScript code, mostly because it was
easier to replace the `.surroundings div` selector with `.carousel`, and
because that way i can have a single template that loads and initializes
Slick.
There is no UI to create or edit service definitions, although there are
the SQL functions, because i have no more time now, and Oriol needs to
check that the style is correct for that page.
2023-09-17 01:42:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (page *servicesIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
|
|
|
template.MustRenderAdmin(w, r, user, company, "services/index.gohtml", page)
|
|
|
|
}
|
2023-09-25 18:10:33 +00:00
|
|
|
|
|
|
|
type serviceEntry struct {
|
2024-01-13 00:15:24 +00:00
|
|
|
ID int
|
2024-01-12 20:06:12 +00:00
|
|
|
URL string
|
|
|
|
Icon string
|
|
|
|
Name string
|
2023-09-25 18:10:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func collectServiceEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*serviceEntry, error) {
|
|
|
|
rows, err := conn.Query(ctx, `
|
2024-01-13 00:15:24 +00:00
|
|
|
select service_id
|
|
|
|
, '/admin/services/' || service_id
|
2023-09-25 18:10:33 +00:00
|
|
|
, icon_name
|
2024-01-13 00:15:24 +00:00
|
|
|
, name
|
2023-09-25 18:10:33 +00:00
|
|
|
from service
|
2024-01-13 00:15:24 +00:00
|
|
|
where company_id = $1
|
|
|
|
order by position
|
|
|
|
, name
|
2024-01-12 20:06:12 +00:00
|
|
|
`, company.ID)
|
2023-09-25 18:10:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
var services []*serviceEntry
|
|
|
|
for rows.Next() {
|
|
|
|
entry := &serviceEntry{}
|
2024-01-13 00:15:24 +00:00
|
|
|
if err = rows.Scan(&entry.ID, &entry.URL, &entry.Icon, &entry.Name); err != nil {
|
2023-09-25 18:10:33 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
services = append(services, entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
return services, nil
|
|
|
|
}
|
|
|
|
|
2023-09-26 14:51:35 +00:00
|
|
|
func addService(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
2024-01-12 20:06:12 +00:00
|
|
|
f := newServiceForm(r.Context(), conn, company)
|
|
|
|
processServiceForm(w, r, user, company, conn, f, func(ctx context.Context, tx *database.Tx) error {
|
|
|
|
var err error
|
|
|
|
if f.ID, err = tx.AddService(ctx, company.ID, f.Icon.String(), f.Name[f.DefaultLang].Val, f.Description[f.DefaultLang].Val); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return translateService(ctx, tx, company, f)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func processServiceForm(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, f *serviceForm, act func(context.Context, *database.Tx) error) {
|
2023-09-26 14:51:35 +00:00
|
|
|
if ok, err := form.Handle(f, w, r, user); err != nil {
|
|
|
|
return
|
|
|
|
} else if !ok {
|
|
|
|
f.MustRender(w, r, user, company)
|
|
|
|
return
|
|
|
|
}
|
2024-01-12 20:06:12 +00:00
|
|
|
tx := conn.MustBegin(r.Context())
|
|
|
|
if err := act(r.Context(), tx); err == nil {
|
|
|
|
tx.MustCommit(r.Context())
|
|
|
|
} else {
|
|
|
|
if rErr := tx.Rollback(r.Context()); rErr != nil {
|
|
|
|
err = rErr
|
|
|
|
}
|
|
|
|
panic(err)
|
|
|
|
}
|
2023-09-26 14:51:35 +00:00
|
|
|
httplib.Redirect(w, r, "/admin/services", http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
|
2024-01-12 20:06:12 +00:00
|
|
|
func translateService(ctx context.Context, tx *database.Tx, company *auth.Company, f *serviceForm) error {
|
|
|
|
for lang := range company.Locales {
|
|
|
|
l := lang.String()
|
|
|
|
if l == f.DefaultLang {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := tx.TranslateService(ctx, f.ID, lang, f.Name[l].Val, f.Description[l].Val); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-09-25 18:10:33 +00:00
|
|
|
func editService(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, f *serviceForm) {
|
2024-01-12 20:06:12 +00:00
|
|
|
processServiceForm(w, r, user, company, conn, f, func(ctx context.Context, tx *database.Tx) error {
|
|
|
|
if err := tx.EditService(ctx, f.ID, f.Icon.String(), f.Name[f.DefaultLang].Val, f.Description[f.DefaultLang].Val); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return translateService(ctx, tx, company, f)
|
|
|
|
})
|
2023-09-25 18:10:33 +00:00
|
|
|
if ok, err := form.Handle(f, w, r, user); err != nil {
|
|
|
|
return
|
|
|
|
} else if !ok {
|
|
|
|
f.MustRender(w, r, user, company)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
httplib.Redirect(w, r, "/admin/services", http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
|
2023-09-26 15:29:49 +00:00
|
|
|
func deleteService(w http.ResponseWriter, r *http.Request, user *auth.User, conn *database.Conn, id int) {
|
|
|
|
if err := user.VerifyCSRFToken(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
conn.MustExec(r.Context(), "select remove_service($1)", id)
|
|
|
|
httplib.Redirect(w, r, "/admin/services", http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
|
2023-09-25 18:10:33 +00:00
|
|
|
type serviceForm struct {
|
2024-01-12 20:06:12 +00:00
|
|
|
DefaultLang string
|
2023-09-25 18:10:33 +00:00
|
|
|
ID int
|
|
|
|
Icon *form.Select
|
2024-01-12 20:06:12 +00:00
|
|
|
Name form.I18nInput
|
|
|
|
Description form.I18nInput
|
2023-09-25 18:10:33 +00:00
|
|
|
}
|
|
|
|
|
2024-01-12 20:06:12 +00:00
|
|
|
func newServiceForm(ctx context.Context, conn *database.Conn, company *auth.Company) *serviceForm {
|
2023-09-25 18:10:33 +00:00
|
|
|
return &serviceForm{
|
2024-01-12 20:06:12 +00:00
|
|
|
DefaultLang: company.DefaultLanguage.String(),
|
2023-09-25 18:10:33 +00:00
|
|
|
Icon: &form.Select{
|
|
|
|
Name: "icon",
|
|
|
|
Options: form.MustGetOptions(ctx, conn, "select icon_name, icon_name from icon order by 1"),
|
|
|
|
},
|
2024-01-12 20:06:12 +00:00
|
|
|
Name: form.NewI18nInput(company.Locales, "name"),
|
|
|
|
Description: form.NewI18nInput(company.Locales, "description"),
|
2023-09-25 18:10:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *serviceForm) FillFromDatabase(ctx context.Context, conn *database.Conn, id int) error {
|
|
|
|
f.ID = id
|
2024-01-12 20:06:12 +00:00
|
|
|
var name database.RecordArray
|
|
|
|
var description database.RecordArray
|
2023-09-25 18:10:33 +00:00
|
|
|
row := conn.QueryRow(ctx, `
|
|
|
|
select array[icon_name]
|
2024-01-12 20:06:12 +00:00
|
|
|
, service.name
|
|
|
|
, service.description::text
|
|
|
|
, array_agg((lang_tag, i18n.name))
|
|
|
|
, array_agg((lang_tag, i18n.description::text))
|
2023-09-25 18:10:33 +00:00
|
|
|
from service
|
2024-01-12 20:06:12 +00:00
|
|
|
left join service_i18n as i18n using (service_id)
|
2023-09-25 18:10:33 +00:00
|
|
|
where service_id = $1
|
2024-01-12 20:06:12 +00:00
|
|
|
group by icon_name
|
|
|
|
, service.name
|
|
|
|
, service.description::text
|
|
|
|
`, pgx.QueryResultFormats{pgx.BinaryFormatCode}, id)
|
|
|
|
if err := row.Scan(&f.Icon.Selected, &f.Name[f.DefaultLang].Val, &f.Description[f.DefaultLang].Val, &name, &description); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := f.Name.FillArray(name); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := f.Description.FillArray(description); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2023-09-25 18:10:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *serviceForm) Parse(r *http.Request) error {
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
f.Icon.FillValue(r)
|
|
|
|
f.Name.FillValue(r)
|
|
|
|
f.Description.FillValue(r)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *serviceForm) Valid(l *locale.Locale) bool {
|
|
|
|
v := form.NewValidator(l)
|
|
|
|
v.CheckSelectedOptions(f.Icon, l.GettextNoop("Selected icon is not valid."))
|
2024-01-12 20:06:12 +00:00
|
|
|
v.CheckRequired(f.Name[f.DefaultLang], l.GettextNoop("Name can not be empty."))
|
2023-09-25 18:10:33 +00:00
|
|
|
return v.AllOK
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *serviceForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
|
|
|
template.MustRenderAdmin(w, r, user, company, "services/form.gohtml", f)
|
|
|
|
}
|