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:
|
|
|
|
f := newServiceForm(r.Context(), conn)
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
f := newServiceForm(r.Context(), conn)
|
|
|
|
if err := f.FillFromDatabase(r.Context(), conn, id); err != nil {
|
|
|
|
if database.ErrorIsNotFound(err) {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var langTag string
|
|
|
|
langTag, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
|
|
|
|
|
|
|
switch langTag {
|
|
|
|
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:
|
|
|
|
loc, ok := h.locales.Get(langTag)
|
|
|
|
if !ok {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
l10n := newServiceL10nForm(f, loc)
|
|
|
|
if err := l10n.FillFromDatabase(r.Context(), conn); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodGet:
|
|
|
|
l10n.MustRender(w, r, user, company)
|
|
|
|
case http.MethodPut:
|
|
|
|
editServiceL10n(w, r, user, company, conn, l10n)
|
|
|
|
default:
|
|
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
URL string
|
|
|
|
Icon string
|
|
|
|
Name string
|
|
|
|
Translations []*locale.Translation
|
|
|
|
}
|
|
|
|
|
|
|
|
func collectServiceEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*serviceEntry, error) {
|
|
|
|
rows, err := conn.Query(ctx, `
|
|
|
|
select '/admin/services/' || service_id
|
|
|
|
, icon_name
|
|
|
|
, service.name
|
|
|
|
, array_agg((lang_tag, endonym, not exists (select 1 from service_i18n as i18n where i18n.service_id = service.service_id and i18n.lang_tag = language.lang_tag))::translation order by endonym)
|
|
|
|
from service
|
|
|
|
join company using (company_id)
|
|
|
|
, language
|
|
|
|
where lang_tag <> default_lang_tag
|
|
|
|
and language.selectable
|
|
|
|
and service.company_id = $1
|
|
|
|
group by service_id
|
|
|
|
, icon_name
|
|
|
|
, service.name
|
|
|
|
order by service.name
|
|
|
|
`, pgx.QueryResultFormats{pgx.BinaryFormatCode}, company.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
var services []*serviceEntry
|
|
|
|
for rows.Next() {
|
|
|
|
entry := &serviceEntry{}
|
|
|
|
var translations database.RecordArray
|
|
|
|
if err = rows.Scan(&entry.URL, &entry.Icon, &entry.Name, &translations); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, el := range translations.Elements {
|
|
|
|
entry.Translations = append(entry.Translations, &locale.Translation{
|
|
|
|
URL: entry.URL + "/" + el.Fields[0].Get().(string),
|
|
|
|
Endonym: el.Fields[1].Get().(string),
|
|
|
|
Missing: el.Fields[2].Get().(bool),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
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) {
|
|
|
|
f := newServiceForm(r.Context(), conn)
|
|
|
|
if ok, err := form.Handle(f, w, r, user); err != nil {
|
|
|
|
return
|
|
|
|
} else if !ok {
|
|
|
|
f.MustRender(w, r, user, company)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
conn.MustExec(r.Context(), "select add_service($1, $2, $3, $4)", company.ID, f.Icon, f.Name, f.Description)
|
|
|
|
httplib.Redirect(w, r, "/admin/services", http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
if ok, err := form.Handle(f, w, r, user); err != nil {
|
|
|
|
return
|
|
|
|
} else if !ok {
|
|
|
|
f.MustRender(w, r, user, company)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
conn.MustExec(r.Context(), "select edit_service($1, $2, $3, $4)", f.ID, f.Icon, f.Name, f.Description)
|
|
|
|
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 {
|
|
|
|
ID int
|
|
|
|
Icon *form.Select
|
|
|
|
Name *form.Input
|
|
|
|
Description *form.Input
|
|
|
|
}
|
|
|
|
|
|
|
|
func newServiceForm(ctx context.Context, conn *database.Conn) *serviceForm {
|
|
|
|
return &serviceForm{
|
|
|
|
Icon: &form.Select{
|
|
|
|
Name: "icon",
|
|
|
|
Options: form.MustGetOptions(ctx, conn, "select icon_name, icon_name from icon order by 1"),
|
|
|
|
},
|
|
|
|
Name: &form.Input{
|
|
|
|
Name: "name",
|
|
|
|
},
|
|
|
|
Description: &form.Input{
|
|
|
|
Name: "description",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *serviceForm) FillFromDatabase(ctx context.Context, conn *database.Conn, id int) error {
|
|
|
|
f.ID = id
|
|
|
|
row := conn.QueryRow(ctx, `
|
|
|
|
select array[icon_name]
|
|
|
|
, name
|
|
|
|
, description
|
|
|
|
from service
|
|
|
|
where service_id = $1
|
|
|
|
`, id)
|
|
|
|
return row.Scan(&f.Icon.Selected, &f.Name.Val, &f.Description.Val)
|
|
|
|
}
|
|
|
|
|
|
|
|
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."))
|
|
|
|
v.CheckRequired(f.Name, l.GettextNoop("Name can not be empty."))
|
|
|
|
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)
|
|
|
|
}
|