From 0bbf46367414eda143f67a2a0b8b6d515a05799a Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 28 Dec 2023 14:18:39 -0500 Subject: [PATCH 01/61] chores!: bump `postgres` to `16.1` Existing databases will prevent this from booting. If you want to stay on postgres 14, modify `docker-compose.yaml`'s `services.db.image`. BREAKING: update postgres image major version from 14 to 16 --- docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index b6814bc..cad1268 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ services: db: - image: 'docker.io/library/postgres:14.8' + image: 'docker.io/library/postgres:16.1' environment: POSTGRES_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" POSTGRES_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}" From a8dcdd03886065637e8788c01ec549ac7a9913ff Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Fri, 5 Jan 2024 14:38:48 -0500 Subject: [PATCH 02/61] =?UTF-8?q?chores!:=20bump=20API=20et=20go=20mod=20?= =?UTF-8?q?=C3=A0=20v6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tag v6.0.0 est sorti mais n'était pas réflété dans le code. BREAKING: API est maintenant exposé sur `/v6` et non `/v5` --- cmd/api.go | 18 +++++++++--------- cmd/web.go | 6 +++--- data/apiclient.go | 10 +++++----- data/data.go | 2 +- go.mod | 2 +- handlers/handlers.go | 2 +- handlers/health.go | 6 +++--- handlers/insert.go | 4 ++-- handlers/read.go | 2 +- main.go | 2 +- responses/list.go | 2 +- web/webhandlers/handlers.go | 2 +- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/cmd/api.go b/cmd/api.go index fb355be..b03be9a 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -6,8 +6,8 @@ import ( "log" "codeberg.org/vlbeaudoin/serpents" - "git.agecem.com/agecem/bottin/v5/data" - "git.agecem.com/agecem/bottin/v5/handlers" + "git.agecem.com/agecem/bottin/v6/data" + "git.agecem.com/agecem/bottin/v6/handlers" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/spf13/cobra" @@ -62,19 +62,19 @@ var apiCmd = &cobra.Command{ // Routes - e.GET("/v5/health/", h.GetHealth) + e.GET("/v6/health/", h.GetHealth) - e.POST("/v5/membres/", h.PostMembres) + e.POST("/v6/membres/", h.PostMembres) - e.GET("/v5/membres/", h.ListMembres) + e.GET("/v6/membres/", h.ListMembres) - e.GET("/v5/membres/:membre_id/", h.ReadMembre) + e.GET("/v6/membres/:membre_id/", h.ReadMembre) - e.PUT("/v5/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) + e.PUT("/v6/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) - e.POST("/v5/programmes/", h.PostProgrammes) + e.POST("/v6/programmes/", h.PostProgrammes) - e.POST("/v5/seed/", h.PostSeed) + e.POST("/v6/seed/", h.PostSeed) // Execution diff --git a/cmd/web.go b/cmd/web.go index 1aa9e31..4d1a556 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -10,9 +10,9 @@ import ( "net/http" "codeberg.org/vlbeaudoin/serpents" - "git.agecem.com/agecem/bottin/v5/data" - "git.agecem.com/agecem/bottin/v5/web" - "git.agecem.com/agecem/bottin/v5/web/webhandlers" + "git.agecem.com/agecem/bottin/v6/data" + "git.agecem.com/agecem/bottin/v6/web" + "git.agecem.com/agecem/bottin/v6/web/webhandlers" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/spf13/cobra" diff --git a/data/apiclient.go b/data/apiclient.go index d5257cf..a068809 100644 --- a/data/apiclient.go +++ b/data/apiclient.go @@ -6,8 +6,8 @@ import ( "net/http" "codeberg.org/vlbeaudoin/voki" - "git.agecem.com/agecem/bottin/v5/models" - "git.agecem.com/agecem/bottin/v5/responses" + "git.agecem.com/agecem/bottin/v6/models" + "git.agecem.com/agecem/bottin/v6/responses" "github.com/spf13/viper" ) @@ -33,7 +33,7 @@ func NewApiClient(client *http.Client, key, host, protocol string, port int) *Ap // GetHealth allows checking for API server health func (a *ApiClient) GetHealth() (string, error) { var getHealthResponse responses.GetHealthResponse - err := a.Voki.Unmarshal(http.MethodGet, "/v5/health", nil, true, &getHealthResponse) + err := a.Voki.Unmarshal(http.MethodGet, "/v6/health", nil, true, &getHealthResponse) if err != nil { return getHealthResponse.Message, err } @@ -57,7 +57,7 @@ func (a *ApiClient) GetMembre(membreID string) (models.Membre, error) { return getMembreResponse.Data.Membre, errors.New("Veuillez fournir un numéro étudiant à rechercher") } - err := a.Voki.Unmarshal(http.MethodGet, fmt.Sprintf("/v5/membres/%s", membreID), nil, true, &getMembreResponse) + err := a.Voki.Unmarshal(http.MethodGet, fmt.Sprintf("/v6/membres/%s", membreID), nil, true, &getMembreResponse) if err != nil { return getMembreResponse.Data.Membre, err } @@ -70,5 +70,5 @@ func (a *ApiClient) GetMembre(membreID string) (models.Membre, error) { } func (a *ApiClient) ListMembres() (r responses.ListMembresResponse, err error) { - return r, a.Voki.Unmarshal(http.MethodGet, "/v5/membres", nil, true, &r) + return r, a.Voki.Unmarshal(http.MethodGet, "/v6/membres", nil, true, &r) } diff --git a/data/data.go b/data/data.go index 98c293b..c4cc026 100644 --- a/data/data.go +++ b/data/data.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "git.agecem.com/agecem/bottin/v5/models" + "git.agecem.com/agecem/bottin/v6/models" _ "github.com/jackc/pgx/stdlib" "github.com/jmoiron/sqlx" "github.com/spf13/viper" diff --git a/go.mod b/go.mod index 55c8485..014c923 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.agecem.com/agecem/bottin/v5 +module git.agecem.com/agecem/bottin/v6 go 1.21.1 diff --git a/handlers/handlers.go b/handlers/handlers.go index 7bbb88f..0b35b23 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -1,6 +1,6 @@ package handlers -import "git.agecem.com/agecem/bottin/v5/data" +import "git.agecem.com/agecem/bottin/v6/data" type Handler struct { DataClient *data.DataClient diff --git a/handlers/health.go b/handlers/health.go index 9e58673..4923493 100644 --- a/handlers/health.go +++ b/handlers/health.go @@ -3,8 +3,8 @@ package handlers import ( "net/http" - "git.agecem.com/agecem/bottin/v5/data" - "git.agecem.com/agecem/bottin/v5/responses" + "git.agecem.com/agecem/bottin/v6/data" + "git.agecem.com/agecem/bottin/v6/responses" "github.com/labstack/echo/v4" ) @@ -30,7 +30,7 @@ func (h *Handler) GetHealth(c echo.Context) error { } response.StatusCode = http.StatusOK - response.Message = "Bottin API v5 is ready" + response.Message = "Bottin API v6 is ready" return c.JSON(response.StatusCode, response) } diff --git a/handlers/insert.go b/handlers/insert.go index 9a1c79b..c4654b9 100644 --- a/handlers/insert.go +++ b/handlers/insert.go @@ -5,8 +5,8 @@ import ( "io" "net/http" - "git.agecem.com/agecem/bottin/v5/models" - "git.agecem.com/agecem/bottin/v5/responses" + "git.agecem.com/agecem/bottin/v6/models" + "git.agecem.com/agecem/bottin/v6/responses" "github.com/labstack/echo/v4" "github.com/gocarina/gocsv" diff --git a/handlers/read.go b/handlers/read.go index 8fec2b1..0a5b1fd 100644 --- a/handlers/read.go +++ b/handlers/read.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - "git.agecem.com/agecem/bottin/v5/responses" + "git.agecem.com/agecem/bottin/v6/responses" "github.com/labstack/echo/v4" ) diff --git a/main.go b/main.go index 567ba46..f0b7d52 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,6 @@ package main -import "git.agecem.com/agecem/bottin/v5/cmd" +import "git.agecem.com/agecem/bottin/v6/cmd" func main() { cmd.Execute() diff --git a/responses/list.go b/responses/list.go index 04df99f..4783e90 100644 --- a/responses/list.go +++ b/responses/list.go @@ -2,7 +2,7 @@ package responses import ( "codeberg.org/vlbeaudoin/voki/response" - "git.agecem.com/agecem/bottin/v5/models" + "git.agecem.com/agecem/bottin/v6/models" ) type ListMembresResponse struct { diff --git a/web/webhandlers/handlers.go b/web/webhandlers/handlers.go index 96fba50..348e43a 100644 --- a/web/webhandlers/handlers.go +++ b/web/webhandlers/handlers.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - "git.agecem.com/agecem/bottin/v5/data" + "git.agecem.com/agecem/bottin/v6/data" "github.com/labstack/echo/v4" ) From b8f05cb2664ed95551246a3a152fe70d9e1ace95 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Fri, 5 Jan 2024 14:49:44 -0500 Subject: [PATCH 03/61] fix: Identifier formats `json` et `csv` permis lors d'insertion Clarifier le message d'erreur d'insertion `Invalid Content-Type` pour inclure les formats permis --- handlers/insert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/insert.go b/handlers/insert.go index c4654b9..8039505 100644 --- a/handlers/insert.go +++ b/handlers/insert.go @@ -43,7 +43,7 @@ func (h *Handler) PostMembres(c echo.Context) error { } default: response.StatusCode = http.StatusBadRequest - response.Message = "Invalid Content-Type" + response.Message = "Invalid Content-Type: Please use application/json or text/csv" return c.JSON(response.StatusCode, response) } From 50155ed9cb411bae151b5a1aafc0a8a3867d9f99 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Fri, 5 Jan 2024 15:18:21 -0500 Subject: [PATCH 04/61] license: remplacer license pour GNU GPLv2 --- LICENSE | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index b5cc18d..7d6e2c2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,118 @@ -MIT License +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 -Copyright (c) 2021-2023 AGECEM +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + + c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the program's name and an idea of what it does. Copyright (C) yyyy name of author + + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. + +signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From f7437d17196bb8e682d96b8127578415aaf942b8 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 14 Feb 2024 14:05:04 -0500 Subject: [PATCH 05/61] feat: Permettre de configurer `api` et `web` par `.env` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'ajout à viper de replacer et préfixe `BOTTIN` permet de déployer et configurer l'application avec seulement docker-compose, en évitant d'avoir à nécessairement uploader un fichier de config. Ajoute aussi des explications dans `README.md` sur changements de procédure --- README.md | 7 +++++++ cmd/root.go | 3 +++ docker-compose.yaml | 9 +++++++++ 3 files changed, 19 insertions(+) diff --git a/README.md b/README.md index 74d3152..acda47f 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,22 @@ Remplir .env avec les infos qui seront utilisées pour déployer le container (Remplacer `bottin` par quelque chose de plus sécuritaire) ```sh +BOTTIN_API_KEY=bottin BOTTIN_POSTGRES_DATABASE=bottin BOTTIN_POSTGRES_PASSWORD=bottin BOTTIN_POSTGRES_USER=bottin +BOTTIN_WEB_PASSWORD=bottin +BOTTIN_WEB_USER=bottin ``` Déployer avec docker-compose `$ docker-compose up -d` +### Optionnel: configuration par fichiers YAML + +*seulement nécessaire si les fichiers `.env` et `docker-compose.yaml` ne contiennent pas toute l'information nécessaire* + Pour modifier la configuration du serveur API `$ docker-compose exec -it api vi /etc/bottin/api.yaml` diff --git a/cmd/root.go b/cmd/root.go index 94350ca..0b7562e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "os" + "strings" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -47,6 +48,8 @@ func initConfig() { viper.SetConfigName(".bottin") } + viper.SetEnvPrefix("BOTTIN") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. diff --git a/docker-compose.yaml b/docker-compose.yaml index cad1268..1f739d8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,6 +15,11 @@ services: - db build: . image: 'git.agecem.com/agecem/bottin:latest' + environment: + BOTTIN_DB_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" + BOTTIN_DB_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}" + BOTTIN_DB_USER: "${BOTTIN_POSTGRES_USER}" + BOTTIN_API_KEY: "${BOTTIN_API_KEY}" ports: - '1312:1312' volumes: @@ -27,6 +32,10 @@ services: - api build: . image: 'git.agecem.com/agecem/bottin:latest' + environment: + BOTTIN_WEB_API_KEY: "${BOTTIN_API_KEY}" + BOTTIN_WEB_PASSWORD: "${BOTTIN_WEB_PASSWORD}" + BOTTIN_WEB_USER: "${BOTTIN_WEB_USER}" ports: - '2312:2312' volumes: From d5399903e4b6f96bda279b4f991ed148af7b69a7 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 14 Feb 2024 14:13:01 -0500 Subject: [PATCH 06/61] =?UTF-8?q?fix:=20defer=20certains=20appels=20=C3=A0?= =?UTF-8?q?=20`tx.Rollback`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pour `data.InsertMembres` et `data.InsertProgrammes` --- data/data.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/data/data.go b/data/data.go index c4cc026..fa8678c 100644 --- a/data/data.go +++ b/data/data.go @@ -78,24 +78,21 @@ func (d *DataClient) InsertMembres(membres []models.Membre) (int64, error) { var rowsInserted int64 tx, err := d.DB.Beginx() if err != nil { - tx.Rollback() return rowsInserted, err } + defer tx.Rollback() for _, membre := range membres { if membre.ID == "" { - tx.Rollback() return 0, errors.New("Cannot insert membre with no membre_id") } result, err := tx.NamedExec("INSERT INTO membres (id, last_name, first_name, prefered_name, programme_id) VALUES (:id, :last_name, :first_name, :prefered_name, :programme_id) ON CONFLICT (id) DO NOTHING;", &membre) if err != nil { - tx.Rollback() return 0, err } rows, err := result.RowsAffected() if err != nil { - tx.Rollback() return 0, err } @@ -114,25 +111,22 @@ func (d *DataClient) InsertProgrammes(programmes []models.Programme) (int64, err var rowsInserted int64 tx, err := d.DB.Beginx() if err != nil { - tx.Rollback() return rowsInserted, err } + defer tx.Rollback() for _, programme := range programmes { if programme.ID == "" { - tx.Rollback() return 0, errors.New("Cannot insert programme with no programme_id") } result, err := tx.NamedExec("INSERT INTO programmes (id, titre) VALUES (:id, :titre) ON CONFLICT DO NOTHING;", &programme) if err != nil { - tx.Rollback() return 0, err } rows, err := result.RowsAffected() if err != nil { - tx.Rollback() return 0, err } From 81d775e5a6a885ec2a3439e0f20943575d358108 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 14 Feb 2024 16:36:44 -0500 Subject: [PATCH 07/61] fix: escalate getmembreresponse message as error if no returned membre --- data/apiclient.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/apiclient.go b/data/apiclient.go index a068809..00fb690 100644 --- a/data/apiclient.go +++ b/data/apiclient.go @@ -63,6 +63,10 @@ func (a *ApiClient) GetMembre(membreID string) (models.Membre, error) { } if getMembreResponse.Data.Membre == *new(models.Membre) { + if getMembreResponse.Message != "" { + return getMembreResponse.Data.Membre, fmt.Errorf(getMembreResponse.Message) + } + return getMembreResponse.Data.Membre, fmt.Errorf("Ce numéro étudiant ne correspond à aucunE membre") } From 4c8e822324a56a48ffd79dcd7ec18e3b2cbb0d99 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 15 Feb 2024 19:26:44 -0500 Subject: [PATCH 08/61] chores: bump `go.mod` dependencies Execute `go get -u` --- go.mod | 44 +++++++++++++++++++++++++------------------- go.sum | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 014c923..e9ff62b 100644 --- a/go.mod +++ b/go.mod @@ -1,46 +1,52 @@ module git.agecem.com/agecem/bottin/v6 -go 1.21.1 +go 1.22.0 require ( - codeberg.org/vlbeaudoin/serpents v1.0.2 - codeberg.org/vlbeaudoin/voki v1.3.1 - github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d + codeberg.org/vlbeaudoin/serpents v1.1.0 + codeberg.org/vlbeaudoin/voki v1.7.2 + github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a github.com/jackc/pgx v3.6.2+incompatible github.com/jmoiron/sqlx v1.3.5 - github.com/labstack/echo/v4 v4.10.2 - github.com/spf13/cobra v1.7.0 - github.com/spf13/viper v1.16.0 + github.com/labstack/echo/v4 v4.11.4 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 ) require ( github.com/cockroachdb/apd v1.1.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect - github.com/labstack/gommon v0.4.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - github.com/spf13/afero v1.9.5 // indirect - github.com/spf13/cast v1.5.1 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/crypto v0.9.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect - golang.org/x/time v0.3.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index bd111f3..57e1bc5 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,10 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= codeberg.org/vlbeaudoin/serpents v1.0.2 h1:mHuL+RBAMOGeiB5+ew1cRputEAnOIQNJW9o9a5Qjudo= codeberg.org/vlbeaudoin/serpents v1.0.2/go.mod h1:3bE/R0ToABwcUJtS1VcGEBa86K5FYhrZGAbFl2qL8kQ= -codeberg.org/vlbeaudoin/voki v1.3.1 h1:TxJj3qmOys0Pbq1dPKnOEXMXKqQLQqrBYd4QqiWWXcw= -codeberg.org/vlbeaudoin/voki v1.3.1/go.mod h1:5XTLx/KiW/OfiupF3o7PAAAU/UhsPdKSrVMmtHbmkPI= +codeberg.org/vlbeaudoin/serpents v1.1.0 h1:U9f2+2D1yUVHx90yePi2ZOLRLG/Wkoob4JXDIVyoBwA= +codeberg.org/vlbeaudoin/serpents v1.1.0/go.mod h1:3bE/R0ToABwcUJtS1VcGEBa86K5FYhrZGAbFl2qL8kQ= +codeberg.org/vlbeaudoin/voki v1.7.2 h1:9sMuOCuJ0t4hhZgr/rbRswHvyXN4CuizGJaB/Exmd90= +codeberg.org/vlbeaudoin/voki v1.7.2/go.mod h1:5XTLx/KiW/OfiupF3o7PAAAU/UhsPdKSrVMmtHbmkPI= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -53,6 +55,7 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -64,8 +67,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -73,6 +79,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d h1:KbPOUXFUDJxwZ04vbmDOc3yuruGvVO+LOa7cVER3yWw= github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= +github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA= +github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -160,8 +168,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= +github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= +github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -173,12 +185,16 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= @@ -189,20 +205,34 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -214,8 +244,11 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -231,6 +264,10 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -240,6 +277,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -250,6 +289,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -307,6 +348,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -366,8 +409,11 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -379,11 +425,15 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 9367f0f4c0ae9523daeda2b4d8b0defead0ce7ac Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 15 Feb 2024 19:32:50 -0500 Subject: [PATCH 09/61] chores: bump voki to v2.0.3 --- data/apiclient.go | 2 +- go.mod | 4 +- go.sum | 495 +------------------------------------------- responses/health.go | 4 +- responses/list.go | 4 +- responses/post.go | 6 +- 6 files changed, 19 insertions(+), 496 deletions(-) diff --git a/data/apiclient.go b/data/apiclient.go index 00fb690..da70a18 100644 --- a/data/apiclient.go +++ b/data/apiclient.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "codeberg.org/vlbeaudoin/voki" + "codeberg.org/vlbeaudoin/voki/v2" "git.agecem.com/agecem/bottin/v6/models" "git.agecem.com/agecem/bottin/v6/responses" "github.com/spf13/viper" diff --git a/go.mod b/go.mod index e9ff62b..bd60d6f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.0 require ( codeberg.org/vlbeaudoin/serpents v1.1.0 - codeberg.org/vlbeaudoin/voki v1.7.2 + codeberg.org/vlbeaudoin/voki/v2 v2.0.3 github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a github.com/jackc/pgx v3.6.2+incompatible github.com/jmoiron/sqlx v1.3.5 @@ -34,12 +34,10 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.19.0 // indirect golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect diff --git a/go.sum b/go.sum index 57e1bc5..b8fe100 100644 --- a/go.sum +++ b/go.sum @@ -1,152 +1,30 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -codeberg.org/vlbeaudoin/serpents v1.0.2 h1:mHuL+RBAMOGeiB5+ew1cRputEAnOIQNJW9o9a5Qjudo= -codeberg.org/vlbeaudoin/serpents v1.0.2/go.mod h1:3bE/R0ToABwcUJtS1VcGEBa86K5FYhrZGAbFl2qL8kQ= codeberg.org/vlbeaudoin/serpents v1.1.0 h1:U9f2+2D1yUVHx90yePi2ZOLRLG/Wkoob4JXDIVyoBwA= codeberg.org/vlbeaudoin/serpents v1.1.0/go.mod h1:3bE/R0ToABwcUJtS1VcGEBa86K5FYhrZGAbFl2qL8kQ= -codeberg.org/vlbeaudoin/voki v1.7.2 h1:9sMuOCuJ0t4hhZgr/rbRswHvyXN4CuizGJaB/Exmd90= -codeberg.org/vlbeaudoin/voki v1.7.2/go.mod h1:5XTLx/KiW/OfiupF3o7PAAAU/UhsPdKSrVMmtHbmkPI= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +codeberg.org/vlbeaudoin/voki/v2 v2.0.3 h1:H3j7yk8uBiDK19OUWAKbYKmw0tsSw4t0LA5lyAfyT3E= +codeberg.org/vlbeaudoin/voki/v2 v2.0.3/go.mod h1:TVdOLAxB94EJkylt5dleJlTkBzuxau8Xwd4TANQIR7U= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d h1:KbPOUXFUDJxwZ04vbmDOc3yuruGvVO+LOa7cVER3yWw= -github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA= github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= @@ -155,53 +33,34 @@ github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yI github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= -github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -213,384 +72,50 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/responses/health.go b/responses/health.go index 1edf34a..59c6fe5 100644 --- a/responses/health.go +++ b/responses/health.go @@ -1,7 +1,7 @@ package responses -import "codeberg.org/vlbeaudoin/voki/response" +import "codeberg.org/vlbeaudoin/voki/v2" type GetHealthResponse struct { - response.ResponseWithError + voki.ResponseWithError } diff --git a/responses/list.go b/responses/list.go index 4783e90..414883b 100644 --- a/responses/list.go +++ b/responses/list.go @@ -1,12 +1,12 @@ package responses import ( - "codeberg.org/vlbeaudoin/voki/response" + "codeberg.org/vlbeaudoin/voki/v2" "git.agecem.com/agecem/bottin/v6/models" ) type ListMembresResponse struct { - response.ResponseWithError + voki.ResponseWithError Data struct { Membres []models.Membre } diff --git a/responses/post.go b/responses/post.go index cfb03af..7f1b7b4 100644 --- a/responses/post.go +++ b/responses/post.go @@ -1,16 +1,16 @@ package responses -import "codeberg.org/vlbeaudoin/voki/response" +import "codeberg.org/vlbeaudoin/voki/v2" type PostMembresResponse struct { - response.ResponseWithError + voki.ResponseWithError Data struct { MembresInserted int64 } } type PostProgrammesResponse struct { - response.ResponseWithError + voki.ResponseWithError Data struct { ProgrammesInserted int64 } From 4a87daae799af4c16f08db944e3b2be78e27240c Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 15 Feb 2024 19:37:06 -0500 Subject: [PATCH 10/61] chores(Dockerfile): bump go version to v1.22.0 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 67e6af6..c83ac91 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21.1 as build +FROM golang:1.22.0 as build LABEL author="vlbeaudoin" From 917aab7e0113ec73f250b284a0b48834b2a1618a Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 15 Feb 2024 19:38:02 -0500 Subject: [PATCH 11/61] chores(Dockerfile): bump alpine to 3.19 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c83ac91..52a9a73 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o bottin . # Alpine -FROM alpine:3.18 +FROM alpine:3.19 WORKDIR /app From 6d98375adb95673f18c98fe8df88589e92440862 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 6 Jun 2024 01:40:56 -0400 Subject: [PATCH 12/61] =?UTF-8?q?d=C3=A9but=20de=20r=C3=A9=C3=A9criture=20?= =?UTF-8?q?pour=20v7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 15 +-- cmd/api.go | 141 ++++++++++++++++-------- cmd/root.go | 2 +- cmd/web.go | 135 +++++++++++++++-------- config.go | 99 +++++++++++++++++ data/apiclient.go | 78 ------------- data/data.go => db.go | 48 +++++--- db_test.go | 45 ++++++++ docker-compose.yaml | 2 +- models/models.go => entity.go | 21 +--- go.mod | 16 +-- go.sum | 40 +++---- handlers/handlers.go | 11 -- handlers/health.go | 36 ------ handlers/insert.go | 129 ---------------------- handlers/read.go | 61 ---------- handlers/seed.go | 24 ---- handlers/update.go | 42 ------- main.go | 16 ++- responses/post.go => responses.go | 13 ++- responses/health.go | 7 -- responses/list.go | 13 --- sql/schema.sql | 12 ++ {web/templates => templates}/index.html | 0 v4/LICENSE | 9 -- v4/README.md | 1 - v4/go.mod | 10 -- web/embed.go | 10 -- web/webhandlers/handlers.go | 46 -------- 29 files changed, 421 insertions(+), 661 deletions(-) create mode 100644 config.go delete mode 100644 data/apiclient.go rename data/data.go => db.go (78%) create mode 100644 db_test.go rename models/models.go => entity.go (61%) delete mode 100644 handlers/handlers.go delete mode 100644 handlers/health.go delete mode 100644 handlers/insert.go delete mode 100644 handlers/read.go delete mode 100644 handlers/seed.go delete mode 100644 handlers/update.go rename responses/post.go => responses.go (59%) delete mode 100644 responses/health.go delete mode 100644 responses/list.go create mode 100644 sql/schema.sql rename {web/templates => templates}/index.html (100%) delete mode 100644 v4/LICENSE delete mode 100644 v4/README.md delete mode 100644 v4/go.mod delete mode 100644 web/embed.go delete mode 100644 web/webhandlers/handlers.go diff --git a/Dockerfile b/Dockerfile index 52a9a73..7c94ff6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,20 @@ -FROM golang:1.22.0 as build +FROM golang:1.22.3 as build LABEL author="vlbeaudoin" WORKDIR /go/src/app -COPY go.mod go.sum main.go ./ +COPY go.mod go.sum db.go entity.go main.go responses.go ./ ADD cmd/ cmd/ -ADD data/ data/ -ADD handlers/ handlers/ -ADD models/ models/ -ADD responses/ responses/ -ADD web/ web/ +ADD templates/ templates/ +ADD sql/ sql/ -RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o bottin . +RUN CGO_ENABLED=0 go build -a -o bottin . # Alpine -FROM alpine:3.19 +FROM alpine:3.20 WORKDIR /app diff --git a/cmd/api.go b/cmd/api.go index b03be9a..1e9f6d8 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -5,9 +5,6 @@ import ( "fmt" "log" - "codeberg.org/vlbeaudoin/serpents" - "git.agecem.com/agecem/bottin/v6/data" - "git.agecem.com/agecem/bottin/v6/handlers" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/spf13/cobra" @@ -19,14 +16,51 @@ var ( apiKey string ) +const ( + ViperAPIPort string = "api.port" + FlagAPIPort string = "api-port" + DefaultAPIPort int = 1312 + DescriptionAPIPort string = "API server port" + + ViperAPIKey string = "api.key" + FlagAPIKey string = "api-key" + DefaultAPIKey string = "bottin" + DescriptionAPIKey string = "API server key. Leave empty for no key auth (not recommended)" + + ViperDBDatabase string = "db.database" + FlagDBDatabase string = "db-database" + DefaultDBDatabase string = "bottin" + DescriptionDBDatabase string = "Postgres database" + + ViperDBHost string = "db.host" + FlagDBHost string = "db-host" + DefaultDBHost string = "db" + DescriptionDBHost string = "Postgres host" + + ViperDBPassword string = "db.password" + FlagDBPassword string = "db-password" + DefaultDBPassword string = "bottin" + DescriptionDBPassword string = "Postgres password" + + ViperDBPort string = "db.port" + FlagDBPort string = "db-port" + DefaultDBPort int = 5432 + DescriptionDBPort string = "Postgres port" + + ViperDBUser string = "db.user" + FlagDBUser string = "db-user" + DefaultDBUser string = "bottin" + DescriptionDBUser string = "Postgres user" +) + // apiCmd represents the api command var apiCmd = &cobra.Command{ Use: "api", Short: "Démarrer le serveur API", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - apiKey = viper.GetString("api.key") - apiPort = viper.GetInt("api.port") + apiKey = viper.GetString(ViperAPIKey) + apiPort = viper.GetInt(ViperAPIPort) e := echo.New() @@ -41,40 +75,42 @@ var apiCmd = &cobra.Command{ } // DataClient + /* + client, err := data.NewDataClientFromViper() + if err != nil { + log.Fatalf("Could not establish database connection.\n Error: %s\n", err) + } + defer client.DB.Close() - client, err := data.NewDataClientFromViper() - if err != nil { - log.Fatalf("Could not establish database connection.\n Error: %s\n", err) - } - defer client.DB.Close() + err = client.DB.Ping() + if err != nil { + log.Fatalf("Database was supposed to be ready but Ping() failed.\n Error: %s\n", err) + } - err = client.DB.Ping() - if err != nil { - log.Fatalf("Database was supposed to be ready but Ping() failed.\n Error: %s\n", err) - } - - _, err = client.Seed() - if err != nil { - log.Fatalf("Error during client.Seed(): %s", err) - } - - h := handlers.New(client) + _, err = client.Seed() + if err != nil { + log.Fatalf("Error during client.Seed(): %s", err) + } + */ // Routes + /* + h := handlers.New(client) - e.GET("/v6/health/", h.GetHealth) + e.GET("/v7/health/", h.GetHealth) - e.POST("/v6/membres/", h.PostMembres) + e.POST("/v7/membres/", h.PostMembres) - e.GET("/v6/membres/", h.ListMembres) + e.GET("/v7/membres/", h.ListMembres) - e.GET("/v6/membres/:membre_id/", h.ReadMembre) + e.GET("/v7/membres/:membre_id/", h.ReadMembre) - e.PUT("/v6/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) + e.PUT("/v7/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) - e.POST("/v6/programmes/", h.PostProgrammes) + e.POST("/v7/programmes/", h.PostProgrammes) - e.POST("/v6/seed/", h.PostSeed) + e.POST("/v7/seed/", h.PostSeed) + */ // Execution @@ -86,37 +122,44 @@ func init() { rootCmd.AddCommand(apiCmd) // api.key - serpents.String(apiCmd.Flags(), - "api.key", "api-key", "bottin", - "API server key. Leave empty for no key auth") + apiCmd.Flags().String(FlagAPIKey, DefaultAPIKey, DescriptionAPIKey) + if err := viper.BindPFlag(ViperAPIKey, apiCmd.Flags().Lookup(FlagAPIKey)); err != nil { + log.Fatal(err) + } // api.port - serpents.Int(apiCmd.Flags(), - "api.port", "api-port", 1312, - "API server port") + apiCmd.Flags().Int(FlagAPIPort, DefaultAPIPort, DescriptionAPIPort) + if err := viper.BindPFlag(ViperAPIPort, apiCmd.Flags().Lookup(FlagAPIPort)); err != nil { + log.Fatal(err) + } // db.database - serpents.String(apiCmd.Flags(), - "db.database", "db-database", "bottin", - "Postgres database") + apiCmd.Flags().String(FlagDBDatabase, DefaultDBDatabase, DescriptionDBDatabase) + if err := viper.BindPFlag(ViperDBDatabase, apiCmd.Flags().Lookup(FlagDBDatabase)); err != nil { + log.Fatal(err) + } // db.host - serpents.String(apiCmd.Flags(), - "db.host", "db-host", "db", - "Postgres host") + apiCmd.Flags().String(FlagDBHost, DefaultDBHost, DescriptionDBHost) + if err := viper.BindPFlag(ViperDBHost, apiCmd.Flags().Lookup(FlagDBHost)); err != nil { + log.Fatal(err) + } // db.password - serpents.String(apiCmd.Flags(), - "db.password", "db-password", "bottin", - "Postgres password") + apiCmd.Flags().String(FlagDBPassword, DefaultDBPassword, DescriptionDBPassword) + if err := viper.BindPFlag(ViperDBPassword, apiCmd.Flags().Lookup(FlagDBPassword)); err != nil { + log.Fatal(err) + } // db.port - serpents.Int(apiCmd.Flags(), - "db.port", "db-port", 5432, - "Postgres port") + apiCmd.Flags().Int(FlagDBPort, DefaultDBPort, DescriptionDBPort) + if err := viper.BindPFlag(ViperDBPort, apiCmd.Flags().Lookup(FlagDBPort)); err != nil { + log.Fatal(err) + } // db.user - serpents.String(apiCmd.Flags(), - "db.user", "db-user", "bottin", - "Postgres user") + apiCmd.Flags().String(FlagDBUser, DefaultDBUser, DescriptionDBUser) + if err := viper.BindPFlag(ViperDBUser, apiCmd.Flags().Lookup(FlagDBUser)); err != nil { + log.Fatal(err) + } } diff --git a/cmd/root.go b/cmd/root.go index 0b7562e..26e0636 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,7 +14,7 @@ var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "bottin", - Short: "Application de gestion de distribution d'agendas", + Short: "Bottin étudiant de l'AGECEM", } // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/cmd/web.go b/cmd/web.go index 4d1a556..2ce2fad 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -7,12 +7,7 @@ import ( "html/template" "io" "log" - "net/http" - "codeberg.org/vlbeaudoin/serpents" - "git.agecem.com/agecem/bottin/v6/data" - "git.agecem.com/agecem/bottin/v6/web" - "git.agecem.com/agecem/bottin/v6/web/webhandlers" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/spf13/cobra" @@ -29,6 +24,43 @@ var ( webApiProtocol string ) +const ( + viperWebUser string = "web.user" + flagWebUser string = "web-user" + defaultWebUser string = "bottin" + descriptionWebUser string = "Web client basic auth user" + + viperWebPassword string = "web.password" + flagWebPassword string = "web-password" + defaultWebPassword string = "bottin" + descriptionWebPassword string = "Web client basic auth password" + + viperWebPort string = "web.port" + flagWebPort string = "web-port" + defaultWebPort int = 2312 + descriptionWebPort string = "Web client port" + + viperWebAPIHost string = "api.host" + flagWebAPIHost string = "api-host" + defaultWebAPIHost string = "api" + descriptionWebAPIHost string = "Target API server host" + + viperWebAPIKey string = "api.key" + flagWebAPIKey string = "api-key" + defaultWebAPIKey string = "bottin" + descriptionWebAPIKey string = "Target API server key" + + viperWebAPIPort string = "api.port" + flagWebAPIPort string = "api-port" + defaultWebAPIPort int = 1312 + descriptionWebAPIPort string = "Target API server port" + + viperWebAPIProtocol string = "api.protocol" + flagWebAPIProtocol string = "api-protocol" + defaultWebAPIProtocol string = "http" + descriptionWebAPIProtocol string = "Target API server protocol (http/https)" +) + var templatesFS embed.FS type Template struct { @@ -45,27 +77,28 @@ var webCmd = &cobra.Command{ Short: "Démarrer le client web", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - webApiHost = viper.GetString("web.api.host") - webApiKey = viper.GetString("web.api.key") - webApiPort = viper.GetInt("web.api.port") - webApiProtocol = viper.GetString("web.api.protocol") - webPassword = viper.GetString("web.password") - webPort = viper.GetInt("web.port") - webUser = viper.GetString("web.user") + webApiHost = viper.GetString(viperWebAPIHost) + webApiKey = viper.GetString(viperWebAPIKey) + webApiPort = viper.GetInt(viperWebAPIPort) + webApiProtocol = viper.GetString(viperWebAPIProtocol) + webPassword = viper.GetString(viperWebPassword) + webPort = viper.GetInt(viperWebPort) + webUser = viper.GetString(viperWebUser) // Ping API server + /* + client := http.DefaultClient + defer client.CloseIdleConnections() - client := http.DefaultClient - defer client.CloseIdleConnections() + apiClient := data.NewApiClient(client, webApiKey, webApiHost, webApiProtocol, webApiPort) - apiClient := data.NewApiClient(client, webApiKey, webApiHost, webApiProtocol, webApiPort) + pingResult, err := apiClient.GetHealth() + if err != nil { + log.Fatal(err) + } - pingResult, err := apiClient.GetHealth() - if err != nil { - log.Fatal(err) - } - - log.Println(pingResult) + log.Println(pingResult) + */ e := echo.New() @@ -88,11 +121,12 @@ var webCmd = &cobra.Command{ e.Renderer = t // Routes + /* + handler := webhandlers.Handler{APIClient: apiClient} - handler := webhandlers.Handler{APIClient: apiClient} - - e.GET("/", handler.GetIndex) - e.GET("/membre/", handler.GetMembre) + e.GET("/", handler.GetIndex) + e.GET("/membre/", handler.GetMembre) + */ // Execution @@ -103,40 +137,47 @@ var webCmd = &cobra.Command{ func init() { rootCmd.AddCommand(webCmd) - templatesFS = web.GetTemplates() + //templatesFS = web.GetTemplates() // web.api.host - serpents.String(webCmd.Flags(), - "web.api.host", "web-api-host", "api", - "Remote API server host") + webCmd.Flags().String(flagWebAPIHost, defaultWebAPIHost, descriptionWebAPIHost) + if err := viper.BindPFlag(viperWebAPIHost, webCmd.Flags().Lookup(flagWebAPIHost)); err != nil { + log.Fatal(err) + } // web.api.key - serpents.String(webCmd.Flags(), - "web.api.key", "web-api-key", "bottin", - "Remote API server key") + webCmd.Flags().String(flagWebAPIKey, defaultWebAPIKey, descriptionWebAPIKey) + if err := viper.BindPFlag(viperWebAPIKey, webCmd.Flags().Lookup(flagWebAPIKey)); err != nil { + log.Fatal(err) + } // web.api.protocol - serpents.String(webCmd.Flags(), - "web.api.protocol", "web-api-protocol", "http", - "Remote API server protocol") + webCmd.Flags().String(flagWebAPIProtocol, defaultWebAPIProtocol, descriptionWebAPIProtocol) + if err := viper.BindPFlag(viperWebAPIProtocol, webCmd.Flags().Lookup(flagWebAPIProtocol)); err != nil { + log.Fatal(err) + } // web.api.port - serpents.Int(webCmd.Flags(), - "web.api.port", "web-api-port", 1312, - "Remote API server port") + webCmd.Flags().Int(flagWebAPIPort, defaultWebAPIPort, descriptionWebAPIPort) + if err := viper.BindPFlag(viperWebAPIPort, webCmd.Flags().Lookup(flagWebAPIPort)); err != nil { + log.Fatal(err) + } // web.password - serpents.String(webCmd.Flags(), - "web.password", "web-password", "bottin", - "Web client password") + webCmd.Flags().String(flagWebPassword, defaultWebPassword, descriptionWebPassword) + if err := viper.BindPFlag(viperWebPassword, webCmd.Flags().Lookup(flagWebPassword)); err != nil { + log.Fatal(err) + } // web.port - serpents.Int(webCmd.Flags(), - "web.port", "web-port", 2312, - "Web client port") + webCmd.Flags().Int(flagWebPort, defaultWebPort, descriptionWebPort) + if err := viper.BindPFlag(viperWebPort, webCmd.Flags().Lookup(flagWebPort)); err != nil { + log.Fatal(err) + } // web.user - serpents.String(webCmd.Flags(), - "web.user", "web-user", "bottin", - "Web client user") + webCmd.Flags().String(flagWebUser, defaultWebUser, descriptionWebUser) + if err := viper.BindPFlag(viperWebUser, webCmd.Flags().Lookup(flagWebUser)); err != nil { + log.Fatal(err) + } } diff --git a/config.go b/config.go new file mode 100644 index 0000000..c0108b4 --- /dev/null +++ b/config.go @@ -0,0 +1,99 @@ +package main + +const ( + ViperAPIPort string = "api.port" + FlagAPIPort string = "api-port" + DefaultAPIPort int = 1312 + DescriptionAPIPort string = "API server port" + + ViperAPIKey string = "api.key" + FlagAPIKey string = "api-key" + DefaultAPIKey string = "bottin" + DescriptionAPIKey string = "API server key. Leave empty for no key auth (not recommended)" + + ViperDBDatabase string = "db.database" + FlagDBDatabase string = "db-database" + DefaultDBDatabase string = "bottin" + DescriptionDBDatabase string = "Postgres database" + + ViperDBHost string = "db.host" + FlagDBHost string = "db-host" + DefaultDBHost string = "db" + DescriptionDBHost string = "Postgres host" + + ViperDBPassword string = "db.password" + FlagDBPassword string = "db-password" + DefaultDBPassword string = "bottin" + DescriptionDBPassword string = "Postgres password" + + ViperDBPort string = "db.port" + FlagDBPort string = "db-port" + DefaultDBPort int = 5432 + DescriptionDBPort string = "Postgres port" + + ViperDBUser string = "db.user" + FlagDBUser string = "db-user" + DefaultDBUser string = "bottin" + DescriptionDBUser string = "Postgres user" + + viperWebUser string = "web.user" + flagWebUser string = "web-user" + defaultWebUser string = "bottin" + descriptionWebUser string = "Web client basic auth user" + + viperWebPassword string = "web.password" + flagWebPassword string = "web-password" + defaultWebPassword string = "bottin" + descriptionWebPassword string = "Web client basic auth password" + + viperWebPort string = "web.port" + flagWebPort string = "web-port" + defaultWebPort int = 2312 + descriptionWebPort string = "Web client port" + + viperWebAPIHost string = "api.host" + flagWebAPIHost string = "api-host" + defaultWebAPIHost string = "api" + descriptionWebAPIHost string = "Target API server host" + + viperWebAPIKey string = "api.key" + flagWebAPIKey string = "api-key" + defaultWebAPIKey string = "bottin" + descriptionWebAPIKey string = "Target API server key" + + viperWebAPIPort string = "api.port" + flagWebAPIPort string = "api-port" + defaultWebAPIPort int = 1312 + descriptionWebAPIPort string = "Target API server port" + + viperWebAPIProtocol string = "api.protocol" + flagWebAPIProtocol string = "api-protocol" + defaultWebAPIProtocol string = "http" + descriptionWebAPIProtocol string = "Target API server protocol (http/https)" +) + +type Config struct { + API struct { + Port int `yaml:"port"` + Key string `yaml:"key"` + } `yaml:"api"` + DB struct { + Database string `yaml:"database"` + Host string `yaml:"host"` + SSLMode string `yaml:"sslmode"` + Password string `yaml:"password"` + Port int `yaml:"port"` + User string `yaml:"user"` + } `yaml:"db"` + Web struct { + User string `yaml:"user"` + Password string `yaml:"password"` + Port int `yaml:"port"` + API struct { + Host string `yaml:"host"` + Key string `yaml:"key"` + Port int `yaml:"port"` + Protocol string `yaml:"protocol"` + } `yaml:"api"` + } `yaml:"web"` +} diff --git a/data/apiclient.go b/data/apiclient.go deleted file mode 100644 index da70a18..0000000 --- a/data/apiclient.go +++ /dev/null @@ -1,78 +0,0 @@ -package data - -import ( - "errors" - "fmt" - "net/http" - - "codeberg.org/vlbeaudoin/voki/v2" - "git.agecem.com/agecem/bottin/v6/models" - "git.agecem.com/agecem/bottin/v6/responses" - "github.com/spf13/viper" -) - -type ApiClient struct { - Voki *voki.Voki -} - -func NewApiClientFromViper(client *http.Client) *ApiClient { - apiClientKey := viper.GetString("web.api.key") - apiClientHost := viper.GetString("web.api.host") - apiClientProtocol := viper.GetString("web.api.protocol") - apiClientPort := viper.GetInt("web.api.port") - - return NewApiClient(client, apiClientKey, apiClientHost, apiClientProtocol, apiClientPort) -} - -func NewApiClient(client *http.Client, key, host, protocol string, port int) *ApiClient { - return &ApiClient{ - Voki: voki.New(client, host, key, port, protocol), - } -} - -// GetHealth allows checking for API server health -func (a *ApiClient) GetHealth() (string, error) { - var getHealthResponse responses.GetHealthResponse - err := a.Voki.Unmarshal(http.MethodGet, "/v6/health", nil, true, &getHealthResponse) - if err != nil { - return getHealthResponse.Message, err - } - - if getHealthResponse.Message == "" { - return getHealthResponse.Message, errors.New("Could not confirm that API server is up, no response message") - } - - return getHealthResponse.Message, nil -} - -func (a *ApiClient) GetMembre(membreID string) (models.Membre, error) { - var getMembreResponse struct { - Message string `json:"message"` - Data struct { - Membre models.Membre `json:"membre"` - } `json:"data"` - } - - if membreID == "" { - return getMembreResponse.Data.Membre, errors.New("Veuillez fournir un numéro étudiant à rechercher") - } - - err := a.Voki.Unmarshal(http.MethodGet, fmt.Sprintf("/v6/membres/%s", membreID), nil, true, &getMembreResponse) - if err != nil { - return getMembreResponse.Data.Membre, err - } - - if getMembreResponse.Data.Membre == *new(models.Membre) { - if getMembreResponse.Message != "" { - return getMembreResponse.Data.Membre, fmt.Errorf(getMembreResponse.Message) - } - - return getMembreResponse.Data.Membre, fmt.Errorf("Ce numéro étudiant ne correspond à aucunE membre") - } - - return getMembreResponse.Data.Membre, nil -} - -func (a *ApiClient) ListMembres() (r responses.ListMembresResponse, err error) { - return r, a.Voki.Unmarshal(http.MethodGet, "/v6/membres", nil, true, &r) -} diff --git a/data/data.go b/db.go similarity index 78% rename from data/data.go rename to db.go index fa8678c..fff8885 100644 --- a/data/data.go +++ b/db.go @@ -1,15 +1,26 @@ -package data +package main import ( - "errors" - "fmt" + "context" + _ "embed" - "git.agecem.com/agecem/bottin/v6/models" - _ "github.com/jackc/pgx/stdlib" - "github.com/jmoiron/sqlx" - "github.com/spf13/viper" + "github.com/jackc/pgx/v5/pgxpool" ) +//go:embed sql/schema.sql +var sqlSchema string + +type PostgresClient struct { + Ctx context.Context + Pool *pgxpool.Pool +} + +func (db *PostgresClient) CreateOrReplaceSchema() error { + _, err := db.Pool.Exec(db.Ctx, sqlSchema) + return err +} + +/* // DataClient is a postgres client based on sqlx type DataClient struct { PostgresConnection PostgresConnection @@ -28,11 +39,11 @@ type PostgresConnection struct { func NewDataClientFromViper() (*DataClient, error) { client, err := NewDataClient( PostgresConnection{ - User: viper.GetString("db.user"), - Password: viper.GetString("db.password"), - Host: viper.GetString("db.host"), - Database: viper.GetString("db.database"), - Port: viper.GetInt("db.port"), + User: viper.GetString(cmd.ViperDBHost), + Password: viper.GetString(cmd.ViperDBPassword), + Host: viper.GetString(cmd.ViperDBHost), + Database: viper.GetString(cmd.ViperDBDatabase), + Port: viper.GetInt(cmd.ViperDBPort), }) return client, err @@ -60,7 +71,7 @@ func NewDataClient(connection PostgresConnection) (*DataClient, error) { } func (d *DataClient) Seed() (int64, error) { - result, err := d.DB.Exec(models.Schema) + result, err := d.DB.Exec(sqlSchema) if err != nil { return 0, err } @@ -74,7 +85,7 @@ func (d *DataClient) Seed() (int64, error) { } // InsertMembres inserts a slice of Membre into a database, returning the amount inserted and any error encountered -func (d *DataClient) InsertMembres(membres []models.Membre) (int64, error) { +func (d *DataClient) InsertMembres(membres []Membre) (int64, error) { var rowsInserted int64 tx, err := d.DB.Beginx() if err != nil { @@ -107,7 +118,7 @@ func (d *DataClient) InsertMembres(membres []models.Membre) (int64, error) { return rowsInserted, nil } -func (d *DataClient) InsertProgrammes(programmes []models.Programme) (int64, error) { +func (d *DataClient) InsertProgrammes(programmes []Programme) (int64, error) { var rowsInserted int64 tx, err := d.DB.Beginx() if err != nil { @@ -141,8 +152,8 @@ func (d *DataClient) InsertProgrammes(programmes []models.Programme) (int64, err return rowsInserted, nil } -func (d *DataClient) GetMembre(membreID string) (models.Membre, error) { - var membre models.Membre +func (d *DataClient) GetMembre(membreID string) (Membre, error) { + var membre Membre rows, err := d.DB.Queryx("SELECT * FROM membres WHERE id = $1 LIMIT 1;", membreID) if err != nil { @@ -177,6 +188,7 @@ func (d *DataClient) UpdateMembreName(membreID, newName string) (int64, error) { return rows, nil } -func (d *DataClient) GetMembres() (membres []models.Membre, err error) { +func (d *DataClient) GetMembres() (membres []Membre, err error) { return membres, d.DB.Select(&membres, "SELECT * FROM membres;") } +*/ diff --git a/db_test.go b/db_test.go new file mode 100644 index 0000000..d9d235a --- /dev/null +++ b/db_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + "fmt" + "testing" + + "github.com/jackc/pgx/v5/pgxpool" +) + +func TestDB(t *testing.T) { + ctx := context.Background() + + //prep + pool, err := pgxpool.New( + ctx, + fmt.Sprintf( + "user=%s password=%s database=%s host=%s port=%d sslmode=%s ", + "bottin", + "bottin", + "bottin", + "localhost", + 5432, + "prefer", //TODO change to "require" + )) + if err != nil { + t.Error(err) + return + } + defer pool.Close() + + db := &PostgresClient{ + Ctx: ctx, + Pool: pool, + } + + //exec + + t.Run("create or replace schema", + func(t *testing.T) { + if err := db.CreateOrReplaceSchema(); err != nil { + t.Error(err) + } + }) +} diff --git a/docker-compose.yaml b/docker-compose.yaml index 1f739d8..9f2e604 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ services: db: - image: 'docker.io/library/postgres:16.1' + image: 'docker.io/library/postgres:16' environment: POSTGRES_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" POSTGRES_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}" diff --git a/models/models.go b/entity.go similarity index 61% rename from models/models.go rename to entity.go index 845d28d..52e54d0 100644 --- a/models/models.go +++ b/entity.go @@ -1,19 +1,4 @@ -package models - -const Schema = ` -CREATE TABLE IF NOT EXISTS programmes ( - id TEXT PRIMARY KEY, - titre TEXT -); - -CREATE TABLE IF NOT EXISTS membres ( - id VARCHAR(7) PRIMARY KEY, - last_name TEXT, - first_name TEXT, - prefered_name TEXT, - programme_id TEXT REFERENCES programmes(id) -); -` +package main type Programme struct { ID string `db:"id" json:"programme_id" csv:"programme_id"` @@ -27,7 +12,3 @@ type Membre struct { PreferedName string `db:"prefered_name" json:"prefered_name" csv:"prefered_name"` ProgrammeID string `db:"programme_id" json:"programme_id" csv:"programme_id"` } - -type Entry interface { - Programme | Membre -} diff --git a/go.mod b/go.mod index bd60d6f..1952064 100644 --- a/go.mod +++ b/go.mod @@ -1,36 +1,31 @@ -module git.agecem.com/agecem/bottin/v6 +module git.agecem.com/agecem/bottin/v7 go 1.22.0 require ( - codeberg.org/vlbeaudoin/serpents v1.1.0 codeberg.org/vlbeaudoin/voki/v2 v2.0.3 - github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a - github.com/jackc/pgx v3.6.2+incompatible - github.com/jmoiron/sqlx v1.3.5 + github.com/jackc/pgx/v5 v5.6.0 github.com/labstack/echo/v4 v4.11.4 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 ) require ( - github.com/cockroachdb/apd v1.1.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/shopspring/decimal v1.3.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -42,6 +37,7 @@ require ( golang.org/x/crypto v0.19.0 // indirect golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect golang.org/x/net v0.21.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/go.sum b/go.sum index b8fe100..5c5e2b0 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ -codeberg.org/vlbeaudoin/serpents v1.1.0 h1:U9f2+2D1yUVHx90yePi2ZOLRLG/Wkoob4JXDIVyoBwA= -codeberg.org/vlbeaudoin/serpents v1.1.0/go.mod h1:3bE/R0ToABwcUJtS1VcGEBa86K5FYhrZGAbFl2qL8kQ= codeberg.org/vlbeaudoin/voki/v2 v2.0.3 h1:H3j7yk8uBiDK19OUWAKbYKmw0tsSw4t0LA5lyAfyT3E= codeberg.org/vlbeaudoin/voki/v2 v2.0.3/go.mod h1:TVdOLAxB94EJkylt5dleJlTkBzuxau8Xwd4TANQIR7U= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,12 +9,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA= -github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= -github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= -github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -27,12 +17,14 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= -github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= -github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= -github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -41,8 +33,6 @@ github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zG github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= -github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -50,14 +40,10 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -68,8 +54,6 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -85,6 +69,8 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= @@ -103,6 +89,8 @@ golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5C golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= @@ -112,8 +100,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handlers/handlers.go b/handlers/handlers.go deleted file mode 100644 index 0b35b23..0000000 --- a/handlers/handlers.go +++ /dev/null @@ -1,11 +0,0 @@ -package handlers - -import "git.agecem.com/agecem/bottin/v6/data" - -type Handler struct { - DataClient *data.DataClient -} - -func New(dataClient *data.DataClient) *Handler { - return &Handler{DataClient: dataClient} -} diff --git a/handlers/health.go b/handlers/health.go deleted file mode 100644 index 4923493..0000000 --- a/handlers/health.go +++ /dev/null @@ -1,36 +0,0 @@ -package handlers - -import ( - "net/http" - - "git.agecem.com/agecem/bottin/v6/data" - "git.agecem.com/agecem/bottin/v6/responses" - "github.com/labstack/echo/v4" -) - -func (h *Handler) GetHealth(c echo.Context) error { - var response responses.GetHealthResponse - - dataClient, err := data.NewDataClientFromViper() - if err != nil { - response.StatusCode = http.StatusInternalServerError - response.Message = "Error during data.NewDataClientFromViper()" - response.Error = err.Error() - - return c.JSON(response.StatusCode, response) - } - defer dataClient.DB.Close() - - if err = dataClient.DB.Ping(); err != nil { - response.StatusCode = http.StatusInternalServerError - response.Message = "Error during dataClient.DB.Ping()" - response.Error = err.Error() - - return c.JSON(response.StatusCode, response) - } - - response.StatusCode = http.StatusOK - response.Message = "Bottin API v6 is ready" - - return c.JSON(response.StatusCode, response) -} diff --git a/handlers/insert.go b/handlers/insert.go deleted file mode 100644 index 8039505..0000000 --- a/handlers/insert.go +++ /dev/null @@ -1,129 +0,0 @@ -package handlers - -import ( - "encoding/csv" - "io" - "net/http" - - "git.agecem.com/agecem/bottin/v6/models" - "git.agecem.com/agecem/bottin/v6/responses" - "github.com/labstack/echo/v4" - - "github.com/gocarina/gocsv" -) - -func (h *Handler) PostMembres(c echo.Context) error { - var response responses.PostMembresResponse - - var membres []models.Membre - - switch c.Request().Header.Get("Content-Type") { - case "application/json": - if err := c.Bind(&membres); err != nil { - response.StatusCode = http.StatusBadRequest - response.Message = "Could not bind membres" - response.Error = err.Error() - return c.JSON(response.StatusCode, response) - } - case "text/csv": - body := c.Request().Body - if body == nil { - response.StatusCode = http.StatusBadRequest - response.Message = "Request body is empty" - return c.JSON(response.StatusCode, response) - } - defer body.Close() - - // Parse the CSV data from the request body using gocsv. - if err := gocsv.Unmarshal(body, &membres); err != nil { - response.StatusCode = http.StatusBadRequest - response.Message = "Could not unmarshal into membres" - response.Error = err.Error() - return c.JSON(response.StatusCode, response) - } - default: - response.StatusCode = http.StatusBadRequest - response.Message = "Invalid Content-Type: Please use application/json or text/csv" - return c.JSON(response.StatusCode, response) - } - - if len(membres) == 0 { - response.StatusCode = http.StatusOK - response.Message = "Nothing to do" - return c.JSON(response.StatusCode, response) - } - - newMembres, err := h.DataClient.InsertMembres(membres) - if err != nil { - response.StatusCode = http.StatusInternalServerError - response.Message = "Could not insert membres" - response.Error = err.Error() - return c.JSON(response.StatusCode, response) - } - - response.StatusCode = http.StatusCreated - response.Message = "Insert successful" - response.Data.MembresInserted = newMembres - return c.JSON(response.StatusCode, response) -} - -func (h *Handler) PostProgrammes(c echo.Context) error { - var response responses.PostProgrammesResponse - - var programmes []models.Programme - - switch c.Request().Header.Get("Content-Type") { - case "application/json": - if err := c.Bind(&programmes); err != nil { - response.StatusCode = http.StatusBadRequest - response.Message = "Could not bind programmes" - response.Error = err.Error() - return c.JSON(response.StatusCode, response) - } - case "text/csv": - body := c.Request().Body - if body == nil { - response.StatusCode = http.StatusBadRequest - response.Message = "Request body is empty" - return c.JSON(response.StatusCode, response) - } - defer body.Close() - - gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { - r := csv.NewReader(in) - r.Comma = ';' - return r // Allows use ; as delimiter - }) - - // Parse the CSV data from the request body using gocsv. - if err := gocsv.Unmarshal(body, &programmes); err != nil { - response.StatusCode = http.StatusBadRequest - response.Message = "Could not unmarshal into programmes" - response.Error = err.Error() - return c.JSON(response.StatusCode, response) - } - default: - response.StatusCode = http.StatusBadRequest - response.Message = "Invalid Content-Type" - return c.JSON(response.StatusCode, response) - } - - if len(programmes) == 0 { - response.StatusCode = http.StatusOK - response.Message = "Nothing to do" - return c.JSON(response.StatusCode, response) - } - - newProgrammes, err := h.DataClient.InsertProgrammes(programmes) - if err != nil { - response.StatusCode = http.StatusInternalServerError - response.Message = "Could not insert programmes" - response.Error = err.Error() - return c.JSON(response.StatusCode, response) - } - - response.StatusCode = http.StatusCreated - response.Message = "Insert successful" - response.Data.ProgrammesInserted = newProgrammes - return c.JSON(response.StatusCode, response) -} diff --git a/handlers/read.go b/handlers/read.go deleted file mode 100644 index 0a5b1fd..0000000 --- a/handlers/read.go +++ /dev/null @@ -1,61 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - - "git.agecem.com/agecem/bottin/v6/responses" - "github.com/labstack/echo/v4" -) - -func (h *Handler) ReadMembre(c echo.Context) error { - membreID := c.Param("membre_id") - - membre, err := h.DataClient.GetMembre(membreID) - if err != nil { - if err.Error() == "No membre by that id was found" { - return c.JSON(http.StatusNotFound, map[string]string{ - "message": "Not Found", - }) - } - return c.JSON(http.StatusInternalServerError, map[string]string{ - "message": "Unknown error during GetMembre", - "error": err.Error(), - }) - } - - return c.JSON(http.StatusOK, map[string]interface{}{ - "message": "Read successful", - "data": map[string]interface{}{ - "membre": &membre, - }, - }) -} - -func (h *Handler) ListMembres(c echo.Context) error { - var r responses.ListMembresResponse - - membres, err := h.DataClient.GetMembres() - if err != nil { - r.StatusCode = http.StatusInternalServerError - r.Error = err.Error() - r.Message = "Error during (*handlers.Handler).DataClient.GetMembres" - - return c.JSON(r.StatusCode, r) - } - - r.StatusCode = http.StatusOK - - switch membres := len(membres); membres { - case 0: - r.Message = "No membres returned from database" - case 1: - r.Message = "Membre returned from database" - default: - r.Message = fmt.Sprintf("%d membres returned from database", membres) - } - - r.Data.Membres = membres - - return c.JSON(r.StatusCode, r) -} diff --git a/handlers/seed.go b/handlers/seed.go deleted file mode 100644 index 27f5958..0000000 --- a/handlers/seed.go +++ /dev/null @@ -1,24 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/labstack/echo/v4" -) - -func (h *Handler) PostSeed(c echo.Context) error { - rows, err := h.DataClient.Seed() - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{ - "message": "Seed failed", - "error": err.Error(), - }) - } - - return c.JSON(http.StatusOK, map[string]interface{}{ - "message": "Seed successful", - "data": map[string]interface{}{ - "rows": rows, - }, - }) -} diff --git a/handlers/update.go b/handlers/update.go deleted file mode 100644 index 8b4bd04..0000000 --- a/handlers/update.go +++ /dev/null @@ -1,42 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/labstack/echo/v4" -) - -func (h *Handler) PutMembrePreferedName(c echo.Context) error { - membreID := c.Param("membre_id") - - var newName string - - err := c.Bind(&newName) - if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{ - "message": "Could not bind newName", - "error": err.Error(), - }) - } - - rows, err := h.DataClient.UpdateMembreName(membreID, newName) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{ - "message": "Could not update membre name", - "error": err.Error(), - }) - } - - if rows == 0 { - return c.JSON(http.StatusBadRequest, map[string]string{ - "message": "No update was done, probably no membre by that id", - }) - } - - return c.JSON(http.StatusOK, map[string]interface{}{ - "message": "Update successful", - "data": map[string]interface{}{ - "rows": rows, - }, - }) -} diff --git a/main.go b/main.go index f0b7d52..cb9123b 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,19 @@ package main -import "git.agecem.com/agecem/bottin/v6/cmd" +import ( + "context" + "fmt" + "io" + "log" + "os" +) func main() { - cmd.Execute() + if err := Run(context.Background(), Config{}, nil, os.Stdout); err != nil { + log.Fatal(err) + } +} + +func Run(ctx context.Context, config Config, args []string, stdout io.Writer) error { + return fmt.Errorf("not implemented") } diff --git a/responses/post.go b/responses.go similarity index 59% rename from responses/post.go rename to responses.go index 7f1b7b4..d8af880 100644 --- a/responses/post.go +++ b/responses.go @@ -1,7 +1,18 @@ -package responses +package main import "codeberg.org/vlbeaudoin/voki/v2" +type GetHealthResponse struct { + voki.ResponseWithError +} + +type ListMembresResponse struct { + voki.ResponseWithError + Data struct { + Membres []Membre + } +} + type PostMembresResponse struct { voki.ResponseWithError Data struct { diff --git a/responses/health.go b/responses/health.go deleted file mode 100644 index 59c6fe5..0000000 --- a/responses/health.go +++ /dev/null @@ -1,7 +0,0 @@ -package responses - -import "codeberg.org/vlbeaudoin/voki/v2" - -type GetHealthResponse struct { - voki.ResponseWithError -} diff --git a/responses/list.go b/responses/list.go deleted file mode 100644 index 414883b..0000000 --- a/responses/list.go +++ /dev/null @@ -1,13 +0,0 @@ -package responses - -import ( - "codeberg.org/vlbeaudoin/voki/v2" - "git.agecem.com/agecem/bottin/v6/models" -) - -type ListMembresResponse struct { - voki.ResponseWithError - Data struct { - Membres []models.Membre - } -} diff --git a/sql/schema.sql b/sql/schema.sql new file mode 100644 index 0000000..154b983 --- /dev/null +++ b/sql/schema.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS programmes ( + id TEXT PRIMARY KEY, + titre TEXT +); + +CREATE TABLE IF NOT EXISTS membres ( + id VARCHAR(7) PRIMARY KEY, + last_name TEXT, + first_name TEXT, + prefered_name TEXT, + programme_id TEXT REFERENCES programmes(id) +); diff --git a/web/templates/index.html b/templates/index.html similarity index 100% rename from web/templates/index.html rename to templates/index.html diff --git a/v4/LICENSE b/v4/LICENSE deleted file mode 100644 index b5cc18d..0000000 --- a/v4/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) 2021-2023 AGECEM - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/v4/README.md b/v4/README.md deleted file mode 100644 index 4da14f8..0000000 --- a/v4/README.md +++ /dev/null @@ -1 +0,0 @@ -deprecated, see git.agecem.com/agecem/bottin or git.agecem.com/agecem/bottin/v5 diff --git a/v4/go.mod b/v4/go.mod deleted file mode 100644 index 2d01404..0000000 --- a/v4/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module git.agecem.com/agecem/bottin/v4 - -go 1.20 - -//retract ( -// v4.1.0 -// v4.0.3 -// v4.0.2 -// v4.0.1 -//) diff --git a/web/embed.go b/web/embed.go deleted file mode 100644 index 465e5ec..0000000 --- a/web/embed.go +++ /dev/null @@ -1,10 +0,0 @@ -package web - -import "embed" - -//go:embed templates/* -var templatesFS embed.FS - -func GetTemplates() embed.FS { - return templatesFS -} diff --git a/web/webhandlers/handlers.go b/web/webhandlers/handlers.go deleted file mode 100644 index 348e43a..0000000 --- a/web/webhandlers/handlers.go +++ /dev/null @@ -1,46 +0,0 @@ -package webhandlers - -import ( - "fmt" - "net/http" - - "git.agecem.com/agecem/bottin/v6/data" - "github.com/labstack/echo/v4" -) - -type Handler struct { - APIClient *data.ApiClient -} - -func (h *Handler) GetIndex(c echo.Context) error { - return c.Render(http.StatusOK, "index-html", nil) -} - -func (h *Handler) GetMembre(c echo.Context) error { - - membreID := c.QueryParam("membre_id") - - membre, err := h.APIClient.GetMembre(membreID) - if err != nil { - return c.Render(http.StatusBadRequest, "index-html", struct { - Result string - }{ - Result: fmt.Sprintln("👎", err.Error()), - }) - } - - membreResult := fmt.Sprintf(`👍 - Membre trouvéE: [%s]`, membre.ID) - - if membre.PreferedName != "" { - membreResult = fmt.Sprintf("%s -> %s", membreResult, membre.PreferedName) - } else { - membreResult = fmt.Sprintf("%s -> %s, %s", membreResult, membre.LastName, membre.FirstName) - } - - return c.Render(http.StatusOK, "index-html", struct { - Result string - }{ - Result: membreResult, - }) -} From b67955ab283bc119aa64aea82ddb460273de65cd Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 6 Jun 2024 16:28:14 -0400 Subject: [PATCH 13/61] wip: merge cmd package into main package --- Dockerfile | 2 +- cmd.go | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/api.go | 165 --------------------------- cmd/root.go | 59 ---------- cmd/web.go | 183 ------------------------------ db_test.go | 9 +- main.go | 14 ++- 7 files changed, 330 insertions(+), 416 deletions(-) create mode 100644 cmd.go delete mode 100644 cmd/api.go delete mode 100644 cmd/root.go delete mode 100644 cmd/web.go diff --git a/Dockerfile b/Dockerfile index 7c94ff6..361fe34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,8 +7,8 @@ WORKDIR /go/src/app COPY go.mod go.sum db.go entity.go main.go responses.go ./ ADD cmd/ cmd/ -ADD templates/ templates/ ADD sql/ sql/ +ADD templates/ templates/ RUN CGO_ENABLED=0 go build -a -o bottin . diff --git a/cmd.go b/cmd.go new file mode 100644 index 0000000..413e1a2 --- /dev/null +++ b/cmd.go @@ -0,0 +1,314 @@ +package main + +import ( + "crypto/subtle" + "embed" + "fmt" + "html/template" + "io" + "log" + "os" + "strings" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + apiPort int + apiKey string +) + +// apiCmd represents the api command +var apiCmd = &cobra.Command{ + Use: "api", + Short: "Démarrer le serveur API", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + apiKey = viper.GetString(ViperAPIKey) + apiPort = viper.GetInt(ViperAPIPort) + + e := echo.New() + + // Middlewares + + e.Pre(middleware.AddTrailingSlash()) + + if apiKey != "" { + e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { + return subtle.ConstantTimeCompare([]byte(key), []byte(apiKey)) == 1, nil + })) + } + + // DataClient + /* + client, err := data.NewDataClientFromViper() + if err != nil { + log.Fatalf("Could not establish database connection.\n Error: %s\n", err) + } + defer client.DB.Close() + + err = client.DB.Ping() + if err != nil { + log.Fatalf("Database was supposed to be ready but Ping() failed.\n Error: %s\n", err) + } + + _, err = client.Seed() + if err != nil { + log.Fatalf("Error during client.Seed(): %s", err) + } + */ + + // Routes + /* + h := handlers.New(client) + + e.GET("/v7/health/", h.GetHealth) + + e.POST("/v7/membres/", h.PostMembres) + + e.GET("/v7/membres/", h.ListMembres) + + e.GET("/v7/membres/:membre_id/", h.ReadMembre) + + e.PUT("/v7/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) + + e.POST("/v7/programmes/", h.PostProgrammes) + + e.POST("/v7/seed/", h.PostSeed) + */ + + // Execution + + e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", apiPort))) + }, +} + +func init() { + rootCmd.AddCommand(apiCmd) + + // api.key + apiCmd.Flags().String(FlagAPIKey, DefaultAPIKey, DescriptionAPIKey) + if err := viper.BindPFlag(ViperAPIKey, apiCmd.Flags().Lookup(FlagAPIKey)); err != nil { + log.Fatal(err) + } + + // api.port + apiCmd.Flags().Int(FlagAPIPort, DefaultAPIPort, DescriptionAPIPort) + if err := viper.BindPFlag(ViperAPIPort, apiCmd.Flags().Lookup(FlagAPIPort)); err != nil { + log.Fatal(err) + } + + // db.database + apiCmd.Flags().String(FlagDBDatabase, DefaultDBDatabase, DescriptionDBDatabase) + if err := viper.BindPFlag(ViperDBDatabase, apiCmd.Flags().Lookup(FlagDBDatabase)); err != nil { + log.Fatal(err) + } + + // db.host + apiCmd.Flags().String(FlagDBHost, DefaultDBHost, DescriptionDBHost) + if err := viper.BindPFlag(ViperDBHost, apiCmd.Flags().Lookup(FlagDBHost)); err != nil { + log.Fatal(err) + } + + // db.password + apiCmd.Flags().String(FlagDBPassword, DefaultDBPassword, DescriptionDBPassword) + if err := viper.BindPFlag(ViperDBPassword, apiCmd.Flags().Lookup(FlagDBPassword)); err != nil { + log.Fatal(err) + } + + // db.port + apiCmd.Flags().Int(FlagDBPort, DefaultDBPort, DescriptionDBPort) + if err := viper.BindPFlag(ViperDBPort, apiCmd.Flags().Lookup(FlagDBPort)); err != nil { + log.Fatal(err) + } + + // db.user + apiCmd.Flags().String(FlagDBUser, DefaultDBUser, DescriptionDBUser) + if err := viper.BindPFlag(ViperDBUser, apiCmd.Flags().Lookup(FlagDBUser)); err != nil { + log.Fatal(err) + } +} + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "bottin", + Short: "Bottin étudiant de l'AGECEM", +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".bottin" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".bottin") + } + + viper.SetEnvPrefix("BOTTIN") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} + +var ( + webUser string + webPassword string + webPort int + webApiHost string + webApiKey string + webApiPort int + webApiProtocol string +) + +//go:embed templates/* +var templatesFS embed.FS + +type Template struct { + templates *template.Template +} + +func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + return t.templates.ExecuteTemplate(w, name, data) +} + +// webCmd represents the web command +var webCmd = &cobra.Command{ + Use: "web", + Short: "Démarrer le client web", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + webApiHost = viper.GetString(viperWebAPIHost) + webApiKey = viper.GetString(viperWebAPIKey) + webApiPort = viper.GetInt(viperWebAPIPort) + webApiProtocol = viper.GetString(viperWebAPIProtocol) + webPassword = viper.GetString(viperWebPassword) + webPort = viper.GetInt(viperWebPort) + webUser = viper.GetString(viperWebUser) + + // Ping API server + /* + client := http.DefaultClient + defer client.CloseIdleConnections() + + apiClient := data.NewApiClient(client, webApiKey, webApiHost, webApiProtocol, webApiPort) + + pingResult, err := apiClient.GetHealth() + if err != nil { + log.Fatal(err) + } + + log.Println(pingResult) + */ + + e := echo.New() + + // Middlewares + + e.Pre(middleware.AddTrailingSlash()) + + e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { + usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(webUser)) == 1 + passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(webPassword)) == 1 + return usersMatch && passwordsMatch, nil + })) + + // Template + + t := &Template{ + templates: template.Must(template.ParseFS(templatesFS, "templates/*.html")), + } + + e.Renderer = t + + // Routes + /* + handler := webhandlers.Handler{APIClient: apiClient} + + e.GET("/", handler.GetIndex) + e.GET("/membre/", handler.GetMembre) + */ + + // Execution + + e.Logger.Fatal(e.Start( + fmt.Sprintf(":%d", webPort))) + }, +} + +func init() { + rootCmd.AddCommand(webCmd) + //templatesFS = web.GetTemplates() + + // web.api.host + webCmd.Flags().String(flagWebAPIHost, defaultWebAPIHost, descriptionWebAPIHost) + if err := viper.BindPFlag(viperWebAPIHost, webCmd.Flags().Lookup(flagWebAPIHost)); err != nil { + log.Fatal(err) + } + + // web.api.key + webCmd.Flags().String(flagWebAPIKey, defaultWebAPIKey, descriptionWebAPIKey) + if err := viper.BindPFlag(viperWebAPIKey, webCmd.Flags().Lookup(flagWebAPIKey)); err != nil { + log.Fatal(err) + } + + // web.api.protocol + webCmd.Flags().String(flagWebAPIProtocol, defaultWebAPIProtocol, descriptionWebAPIProtocol) + if err := viper.BindPFlag(viperWebAPIProtocol, webCmd.Flags().Lookup(flagWebAPIProtocol)); err != nil { + log.Fatal(err) + } + + // web.api.port + webCmd.Flags().Int(flagWebAPIPort, defaultWebAPIPort, descriptionWebAPIPort) + if err := viper.BindPFlag(viperWebAPIPort, webCmd.Flags().Lookup(flagWebAPIPort)); err != nil { + log.Fatal(err) + } + + // web.password + webCmd.Flags().String(flagWebPassword, defaultWebPassword, descriptionWebPassword) + if err := viper.BindPFlag(viperWebPassword, webCmd.Flags().Lookup(flagWebPassword)); err != nil { + log.Fatal(err) + } + + // web.port + webCmd.Flags().Int(flagWebPort, defaultWebPort, descriptionWebPort) + if err := viper.BindPFlag(viperWebPort, webCmd.Flags().Lookup(flagWebPort)); err != nil { + log.Fatal(err) + } + + // web.user + webCmd.Flags().String(flagWebUser, defaultWebUser, descriptionWebUser) + if err := viper.BindPFlag(viperWebUser, webCmd.Flags().Lookup(flagWebUser)); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/api.go b/cmd/api.go deleted file mode 100644 index 1e9f6d8..0000000 --- a/cmd/api.go +++ /dev/null @@ -1,165 +0,0 @@ -package cmd - -import ( - "crypto/subtle" - "fmt" - "log" - - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var ( - apiPort int - apiKey string -) - -const ( - ViperAPIPort string = "api.port" - FlagAPIPort string = "api-port" - DefaultAPIPort int = 1312 - DescriptionAPIPort string = "API server port" - - ViperAPIKey string = "api.key" - FlagAPIKey string = "api-key" - DefaultAPIKey string = "bottin" - DescriptionAPIKey string = "API server key. Leave empty for no key auth (not recommended)" - - ViperDBDatabase string = "db.database" - FlagDBDatabase string = "db-database" - DefaultDBDatabase string = "bottin" - DescriptionDBDatabase string = "Postgres database" - - ViperDBHost string = "db.host" - FlagDBHost string = "db-host" - DefaultDBHost string = "db" - DescriptionDBHost string = "Postgres host" - - ViperDBPassword string = "db.password" - FlagDBPassword string = "db-password" - DefaultDBPassword string = "bottin" - DescriptionDBPassword string = "Postgres password" - - ViperDBPort string = "db.port" - FlagDBPort string = "db-port" - DefaultDBPort int = 5432 - DescriptionDBPort string = "Postgres port" - - ViperDBUser string = "db.user" - FlagDBUser string = "db-user" - DefaultDBUser string = "bottin" - DescriptionDBUser string = "Postgres user" -) - -// apiCmd represents the api command -var apiCmd = &cobra.Command{ - Use: "api", - Short: "Démarrer le serveur API", - Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, args []string) { - apiKey = viper.GetString(ViperAPIKey) - apiPort = viper.GetInt(ViperAPIPort) - - e := echo.New() - - // Middlewares - - e.Pre(middleware.AddTrailingSlash()) - - if apiKey != "" { - e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { - return subtle.ConstantTimeCompare([]byte(key), []byte(apiKey)) == 1, nil - })) - } - - // DataClient - /* - client, err := data.NewDataClientFromViper() - if err != nil { - log.Fatalf("Could not establish database connection.\n Error: %s\n", err) - } - defer client.DB.Close() - - err = client.DB.Ping() - if err != nil { - log.Fatalf("Database was supposed to be ready but Ping() failed.\n Error: %s\n", err) - } - - _, err = client.Seed() - if err != nil { - log.Fatalf("Error during client.Seed(): %s", err) - } - */ - - // Routes - /* - h := handlers.New(client) - - e.GET("/v7/health/", h.GetHealth) - - e.POST("/v7/membres/", h.PostMembres) - - e.GET("/v7/membres/", h.ListMembres) - - e.GET("/v7/membres/:membre_id/", h.ReadMembre) - - e.PUT("/v7/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) - - e.POST("/v7/programmes/", h.PostProgrammes) - - e.POST("/v7/seed/", h.PostSeed) - */ - - // Execution - - e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", apiPort))) - }, -} - -func init() { - rootCmd.AddCommand(apiCmd) - - // api.key - apiCmd.Flags().String(FlagAPIKey, DefaultAPIKey, DescriptionAPIKey) - if err := viper.BindPFlag(ViperAPIKey, apiCmd.Flags().Lookup(FlagAPIKey)); err != nil { - log.Fatal(err) - } - - // api.port - apiCmd.Flags().Int(FlagAPIPort, DefaultAPIPort, DescriptionAPIPort) - if err := viper.BindPFlag(ViperAPIPort, apiCmd.Flags().Lookup(FlagAPIPort)); err != nil { - log.Fatal(err) - } - - // db.database - apiCmd.Flags().String(FlagDBDatabase, DefaultDBDatabase, DescriptionDBDatabase) - if err := viper.BindPFlag(ViperDBDatabase, apiCmd.Flags().Lookup(FlagDBDatabase)); err != nil { - log.Fatal(err) - } - - // db.host - apiCmd.Flags().String(FlagDBHost, DefaultDBHost, DescriptionDBHost) - if err := viper.BindPFlag(ViperDBHost, apiCmd.Flags().Lookup(FlagDBHost)); err != nil { - log.Fatal(err) - } - - // db.password - apiCmd.Flags().String(FlagDBPassword, DefaultDBPassword, DescriptionDBPassword) - if err := viper.BindPFlag(ViperDBPassword, apiCmd.Flags().Lookup(FlagDBPassword)); err != nil { - log.Fatal(err) - } - - // db.port - apiCmd.Flags().Int(FlagDBPort, DefaultDBPort, DescriptionDBPort) - if err := viper.BindPFlag(ViperDBPort, apiCmd.Flags().Lookup(FlagDBPort)); err != nil { - log.Fatal(err) - } - - // db.user - apiCmd.Flags().String(FlagDBUser, DefaultDBUser, DescriptionDBUser) - if err := viper.BindPFlag(ViperDBUser, apiCmd.Flags().Lookup(FlagDBUser)); err != nil { - log.Fatal(err) - } -} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 26e0636..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,59 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "strings" - - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var cfgFile string - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "bottin", - Short: "Bottin étudiant de l'AGECEM", -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} - -func init() { - cobra.OnInitialize(initConfig) - - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") -} - -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - // Search config in home directory with name ".bottin" (without extension). - viper.AddConfigPath(home) - viper.SetConfigType("yaml") - viper.SetConfigName(".bottin") - } - - viper.SetEnvPrefix("BOTTIN") - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } -} diff --git a/cmd/web.go b/cmd/web.go deleted file mode 100644 index 2ce2fad..0000000 --- a/cmd/web.go +++ /dev/null @@ -1,183 +0,0 @@ -package cmd - -import ( - "crypto/subtle" - "embed" - "fmt" - "html/template" - "io" - "log" - - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var ( - webUser string - webPassword string - webPort int - webApiHost string - webApiKey string - webApiPort int - webApiProtocol string -) - -const ( - viperWebUser string = "web.user" - flagWebUser string = "web-user" - defaultWebUser string = "bottin" - descriptionWebUser string = "Web client basic auth user" - - viperWebPassword string = "web.password" - flagWebPassword string = "web-password" - defaultWebPassword string = "bottin" - descriptionWebPassword string = "Web client basic auth password" - - viperWebPort string = "web.port" - flagWebPort string = "web-port" - defaultWebPort int = 2312 - descriptionWebPort string = "Web client port" - - viperWebAPIHost string = "api.host" - flagWebAPIHost string = "api-host" - defaultWebAPIHost string = "api" - descriptionWebAPIHost string = "Target API server host" - - viperWebAPIKey string = "api.key" - flagWebAPIKey string = "api-key" - defaultWebAPIKey string = "bottin" - descriptionWebAPIKey string = "Target API server key" - - viperWebAPIPort string = "api.port" - flagWebAPIPort string = "api-port" - defaultWebAPIPort int = 1312 - descriptionWebAPIPort string = "Target API server port" - - viperWebAPIProtocol string = "api.protocol" - flagWebAPIProtocol string = "api-protocol" - defaultWebAPIProtocol string = "http" - descriptionWebAPIProtocol string = "Target API server protocol (http/https)" -) - -var templatesFS embed.FS - -type Template struct { - templates *template.Template -} - -func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { - return t.templates.ExecuteTemplate(w, name, data) -} - -// webCmd represents the web command -var webCmd = &cobra.Command{ - Use: "web", - Short: "Démarrer le client web", - Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, args []string) { - webApiHost = viper.GetString(viperWebAPIHost) - webApiKey = viper.GetString(viperWebAPIKey) - webApiPort = viper.GetInt(viperWebAPIPort) - webApiProtocol = viper.GetString(viperWebAPIProtocol) - webPassword = viper.GetString(viperWebPassword) - webPort = viper.GetInt(viperWebPort) - webUser = viper.GetString(viperWebUser) - - // Ping API server - /* - client := http.DefaultClient - defer client.CloseIdleConnections() - - apiClient := data.NewApiClient(client, webApiKey, webApiHost, webApiProtocol, webApiPort) - - pingResult, err := apiClient.GetHealth() - if err != nil { - log.Fatal(err) - } - - log.Println(pingResult) - */ - - e := echo.New() - - // Middlewares - - e.Pre(middleware.AddTrailingSlash()) - - e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { - usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(webUser)) == 1 - passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(webPassword)) == 1 - return usersMatch && passwordsMatch, nil - })) - - // Template - - t := &Template{ - templates: template.Must(template.ParseFS(templatesFS, "templates/*.html")), - } - - e.Renderer = t - - // Routes - /* - handler := webhandlers.Handler{APIClient: apiClient} - - e.GET("/", handler.GetIndex) - e.GET("/membre/", handler.GetMembre) - */ - - // Execution - - e.Logger.Fatal(e.Start( - fmt.Sprintf(":%d", webPort))) - }, -} - -func init() { - rootCmd.AddCommand(webCmd) - //templatesFS = web.GetTemplates() - - // web.api.host - webCmd.Flags().String(flagWebAPIHost, defaultWebAPIHost, descriptionWebAPIHost) - if err := viper.BindPFlag(viperWebAPIHost, webCmd.Flags().Lookup(flagWebAPIHost)); err != nil { - log.Fatal(err) - } - - // web.api.key - webCmd.Flags().String(flagWebAPIKey, defaultWebAPIKey, descriptionWebAPIKey) - if err := viper.BindPFlag(viperWebAPIKey, webCmd.Flags().Lookup(flagWebAPIKey)); err != nil { - log.Fatal(err) - } - - // web.api.protocol - webCmd.Flags().String(flagWebAPIProtocol, defaultWebAPIProtocol, descriptionWebAPIProtocol) - if err := viper.BindPFlag(viperWebAPIProtocol, webCmd.Flags().Lookup(flagWebAPIProtocol)); err != nil { - log.Fatal(err) - } - - // web.api.port - webCmd.Flags().Int(flagWebAPIPort, defaultWebAPIPort, descriptionWebAPIPort) - if err := viper.BindPFlag(viperWebAPIPort, webCmd.Flags().Lookup(flagWebAPIPort)); err != nil { - log.Fatal(err) - } - - // web.password - webCmd.Flags().String(flagWebPassword, defaultWebPassword, descriptionWebPassword) - if err := viper.BindPFlag(viperWebPassword, webCmd.Flags().Lookup(flagWebPassword)); err != nil { - log.Fatal(err) - } - - // web.port - webCmd.Flags().Int(flagWebPort, defaultWebPort, descriptionWebPort) - if err := viper.BindPFlag(viperWebPort, webCmd.Flags().Lookup(flagWebPort)); err != nil { - log.Fatal(err) - } - - // web.user - webCmd.Flags().String(flagWebUser, defaultWebUser, descriptionWebUser) - if err := viper.BindPFlag(viperWebUser, webCmd.Flags().Lookup(flagWebUser)); err != nil { - log.Fatal(err) - } -} diff --git a/db_test.go b/db_test.go index d9d235a..0f508a5 100644 --- a/db_test.go +++ b/db_test.go @@ -8,6 +8,9 @@ import ( "github.com/jackc/pgx/v5/pgxpool" ) +// path to a file containing the db password +var passfile string + func TestDB(t *testing.T) { ctx := context.Background() @@ -17,11 +20,11 @@ func TestDB(t *testing.T) { fmt.Sprintf( "user=%s password=%s database=%s host=%s port=%d sslmode=%s ", "bottin", + dbPassword, "bottin", - "bottin", - "localhost", + "postgres.agecem.com", 5432, - "prefer", //TODO change to "require" + "require", //TODO change to "require" )) if err != nil { t.Error(err) diff --git a/main.go b/main.go index cb9123b..58be335 100644 --- a/main.go +++ b/main.go @@ -4,14 +4,18 @@ import ( "context" "fmt" "io" - "log" - "os" ) func main() { - if err := Run(context.Background(), Config{}, nil, os.Stdout); err != nil { - log.Fatal(err) - } + //TODO + /* + if err := Run(context.Background(), Config{}, nil, os.Stdout); err != nil { + log.Fatal(err) + } + */ + + // Handle the command-line + Execute() } func Run(ctx context.Context, config Config, args []string, stdout io.Writer) error { From 0123d9d37c8ceb356fbe059241b53d2812248203 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 6 Jun 2024 17:01:16 -0400 Subject: [PATCH 14/61] wip: integration between cmd.go and config.go --- cmd.go | 48 +++++++++++++++++-------------- config.go | 84 ++++++++++++++++++++++++++++++++++++------------------ db_test.go | 19 ++++++------ 3 files changed, 93 insertions(+), 58 deletions(-) diff --git a/cmd.go b/cmd.go index 413e1a2..8800c3d 100644 --- a/cmd.go +++ b/cmd.go @@ -107,6 +107,12 @@ func init() { log.Fatal(err) } + // db.sslmode + apiCmd.Flags().String(FlagDBSSLMode, DefaultDBSSLMode, DescriptionDBSSLMode) + if err := viper.BindPFlag(ViperDBSSLMode, apiCmd.Flags().Lookup(FlagDBSSLMode)); err != nil { + log.Fatal(err) + } + // db.host apiCmd.Flags().String(FlagDBHost, DefaultDBHost, DescriptionDBHost) if err := viper.BindPFlag(ViperDBHost, apiCmd.Flags().Lookup(FlagDBHost)); err != nil { @@ -208,13 +214,13 @@ var webCmd = &cobra.Command{ Short: "Démarrer le client web", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - webApiHost = viper.GetString(viperWebAPIHost) - webApiKey = viper.GetString(viperWebAPIKey) - webApiPort = viper.GetInt(viperWebAPIPort) - webApiProtocol = viper.GetString(viperWebAPIProtocol) - webPassword = viper.GetString(viperWebPassword) - webPort = viper.GetInt(viperWebPort) - webUser = viper.GetString(viperWebUser) + webApiHost = viper.GetString(ViperWebAPIHost) + webApiKey = viper.GetString(ViperWebAPIKey) + webApiPort = viper.GetInt(ViperWebAPIPort) + webApiProtocol = viper.GetString(ViperWebAPIProtocol) + webPassword = viper.GetString(ViperWebPassword) + webPort = viper.GetInt(ViperWebPort) + webUser = viper.GetString(ViperWebUser) // Ping API server /* @@ -271,44 +277,44 @@ func init() { //templatesFS = web.GetTemplates() // web.api.host - webCmd.Flags().String(flagWebAPIHost, defaultWebAPIHost, descriptionWebAPIHost) - if err := viper.BindPFlag(viperWebAPIHost, webCmd.Flags().Lookup(flagWebAPIHost)); err != nil { + webCmd.Flags().String(FlagWebAPIHost, DefaultWebAPIHost, DescriptionWebAPIHost) + if err := viper.BindPFlag(ViperWebAPIHost, webCmd.Flags().Lookup(FlagWebAPIHost)); err != nil { log.Fatal(err) } // web.api.key - webCmd.Flags().String(flagWebAPIKey, defaultWebAPIKey, descriptionWebAPIKey) - if err := viper.BindPFlag(viperWebAPIKey, webCmd.Flags().Lookup(flagWebAPIKey)); err != nil { + webCmd.Flags().String(FlagWebAPIKey, DefaultWebAPIKey, DescriptionWebAPIKey) + if err := viper.BindPFlag(ViperWebAPIKey, webCmd.Flags().Lookup(FlagWebAPIKey)); err != nil { log.Fatal(err) } // web.api.protocol - webCmd.Flags().String(flagWebAPIProtocol, defaultWebAPIProtocol, descriptionWebAPIProtocol) - if err := viper.BindPFlag(viperWebAPIProtocol, webCmd.Flags().Lookup(flagWebAPIProtocol)); err != nil { + webCmd.Flags().String(FlagWebAPIProtocol, DefaultWebAPIProtocol, DescriptionWebAPIProtocol) + if err := viper.BindPFlag(ViperWebAPIProtocol, webCmd.Flags().Lookup(FlagWebAPIProtocol)); err != nil { log.Fatal(err) } // web.api.port - webCmd.Flags().Int(flagWebAPIPort, defaultWebAPIPort, descriptionWebAPIPort) - if err := viper.BindPFlag(viperWebAPIPort, webCmd.Flags().Lookup(flagWebAPIPort)); err != nil { + webCmd.Flags().Int(FlagWebAPIPort, DefaultWebAPIPort, DescriptionWebAPIPort) + if err := viper.BindPFlag(ViperWebAPIPort, webCmd.Flags().Lookup(FlagWebAPIPort)); err != nil { log.Fatal(err) } // web.password - webCmd.Flags().String(flagWebPassword, defaultWebPassword, descriptionWebPassword) - if err := viper.BindPFlag(viperWebPassword, webCmd.Flags().Lookup(flagWebPassword)); err != nil { + webCmd.Flags().String(FlagWebPassword, DefaultWebPassword, DescriptionWebPassword) + if err := viper.BindPFlag(ViperWebPassword, webCmd.Flags().Lookup(FlagWebPassword)); err != nil { log.Fatal(err) } // web.port - webCmd.Flags().Int(flagWebPort, defaultWebPort, descriptionWebPort) - if err := viper.BindPFlag(viperWebPort, webCmd.Flags().Lookup(flagWebPort)); err != nil { + webCmd.Flags().Int(FlagWebPort, DefaultWebPort, DescriptionWebPort) + if err := viper.BindPFlag(ViperWebPort, webCmd.Flags().Lookup(FlagWebPort)); err != nil { log.Fatal(err) } // web.user - webCmd.Flags().String(flagWebUser, defaultWebUser, descriptionWebUser) - if err := viper.BindPFlag(viperWebUser, webCmd.Flags().Lookup(flagWebUser)); err != nil { + webCmd.Flags().String(FlagWebUser, DefaultWebUser, DescriptionWebUser) + if err := viper.BindPFlag(ViperWebUser, webCmd.Flags().Lookup(FlagWebUser)); err != nil { log.Fatal(err) } } diff --git a/config.go b/config.go index c0108b4..73d6007 100644 --- a/config.go +++ b/config.go @@ -1,5 +1,7 @@ package main +//TODO move flag declarations here + const ( ViperAPIPort string = "api.port" FlagAPIPort string = "api-port" @@ -16,6 +18,11 @@ const ( DefaultDBDatabase string = "bottin" DescriptionDBDatabase string = "Postgres database" + ViperDBSSLMode string = "db.sslmode" + FlagDBSSLMode string = "db-sslmode" + DefaultDBSSLMode string = "prefer" + DescriptionDBSSLMode string = "Postgres sslmode" + ViperDBHost string = "db.host" FlagDBHost string = "db-host" DefaultDBHost string = "db" @@ -36,40 +43,40 @@ const ( DefaultDBUser string = "bottin" DescriptionDBUser string = "Postgres user" - viperWebUser string = "web.user" - flagWebUser string = "web-user" - defaultWebUser string = "bottin" - descriptionWebUser string = "Web client basic auth user" + ViperWebUser string = "web.user" + FlagWebUser string = "web-user" + DefaultWebUser string = "bottin" + DescriptionWebUser string = "Web client basic auth user" - viperWebPassword string = "web.password" - flagWebPassword string = "web-password" - defaultWebPassword string = "bottin" - descriptionWebPassword string = "Web client basic auth password" + ViperWebPassword string = "web.password" + FlagWebPassword string = "web-password" + DefaultWebPassword string = "bottin" + DescriptionWebPassword string = "Web client basic auth password" - viperWebPort string = "web.port" - flagWebPort string = "web-port" - defaultWebPort int = 2312 - descriptionWebPort string = "Web client port" + ViperWebPort string = "web.port" + FlagWebPort string = "web-port" + DefaultWebPort int = 2312 + DescriptionWebPort string = "Web client port" - viperWebAPIHost string = "api.host" - flagWebAPIHost string = "api-host" - defaultWebAPIHost string = "api" - descriptionWebAPIHost string = "Target API server host" + ViperWebAPIHost string = "api.host" + FlagWebAPIHost string = "api-host" + DefaultWebAPIHost string = "api" + DescriptionWebAPIHost string = "Target API server host" - viperWebAPIKey string = "api.key" - flagWebAPIKey string = "api-key" - defaultWebAPIKey string = "bottin" - descriptionWebAPIKey string = "Target API server key" + ViperWebAPIKey string = "api.key" + FlagWebAPIKey string = "api-key" + DefaultWebAPIKey string = "bottin" + DescriptionWebAPIKey string = "Target API server key" - viperWebAPIPort string = "api.port" - flagWebAPIPort string = "api-port" - defaultWebAPIPort int = 1312 - descriptionWebAPIPort string = "Target API server port" + ViperWebAPIPort string = "api.port" + FlagWebAPIPort string = "api-port" + DefaultWebAPIPort int = 1312 + DescriptionWebAPIPort string = "Target API server port" - viperWebAPIProtocol string = "api.protocol" - flagWebAPIProtocol string = "api-protocol" - defaultWebAPIProtocol string = "http" - descriptionWebAPIProtocol string = "Target API server protocol (http/https)" + ViperWebAPIProtocol string = "api.protocol" + FlagWebAPIProtocol string = "api-protocol" + DefaultWebAPIProtocol string = "http" + DescriptionWebAPIProtocol string = "Target API server protocol (http/https)" ) type Config struct { @@ -97,3 +104,24 @@ type Config struct { } `yaml:"api"` } `yaml:"web"` } + +// DefaultConfig returns a Config filled with the default values from the +// `Default*` constants defined in this file. +func DefaultConfig() (cfg Config) { + cfg.API.Port = DefaultAPIPort + cfg.API.Key = DefaultAPIKey + cfg.DB.Database = DefaultDBDatabase + cfg.DB.Host = DefaultDBHost + cfg.DB.SSLMode = DefaultDBSSLMode + cfg.DB.Password = DefaultDBPassword + cfg.DB.Port = DefaultDBPort + cfg.DB.User = DefaultDBUser + cfg.Web.User = DefaultWebUser + cfg.Web.Password = DefaultWebPassword + cfg.Web.Port = DefaultWebPort + cfg.Web.API.Host = DefaultWebAPIHost + cfg.Web.API.Key = DefaultWebAPIKey + cfg.Web.API.Port = DefaultWebAPIPort + cfg.Web.API.Protocol = DefaultWebAPIProtocol + return +} diff --git a/db_test.go b/db_test.go index 0f508a5..4c3881a 100644 --- a/db_test.go +++ b/db_test.go @@ -6,12 +6,13 @@ import ( "testing" "github.com/jackc/pgx/v5/pgxpool" + "github.com/spf13/viper" ) -// path to a file containing the db password -var passfile string - func TestDB(t *testing.T) { + cfg := DefaultConfig() + cfg.DB.Password = viper.GetString(ViperDBPassword) + ctx := context.Background() //prep @@ -19,12 +20,12 @@ func TestDB(t *testing.T) { ctx, fmt.Sprintf( "user=%s password=%s database=%s host=%s port=%d sslmode=%s ", - "bottin", - dbPassword, - "bottin", - "postgres.agecem.com", - 5432, - "require", //TODO change to "require" + cfg.DB.User, + cfg.DB.Password, + cfg.DB.Database, + cfg.DB.Host, + cfg.DB.Port, + cfg.DB.SSLMode, )) if err != nil { t.Error(err) From cdd526a6f3e17cbd1fd366ce9cb95ecfd40202ec Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 6 Jun 2024 17:59:58 -0400 Subject: [PATCH 15/61] wip: make apiCmd run and remove db test --- client.go | 1 + client_test.go | 1 + cmd.go | 90 ++++++++++++++++++++++++-------------------------- db_test.go | 49 --------------------------- 4 files changed, 46 insertions(+), 95 deletions(-) create mode 100644 client.go create mode 100644 client_test.go delete mode 100644 db_test.go diff --git a/client.go b/client.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/client.go @@ -0,0 +1 @@ +package main diff --git a/client_test.go b/client_test.go new file mode 100644 index 0000000..0fee6f5 --- /dev/null +++ b/client_test.go @@ -0,0 +1 @@ +package main_test diff --git a/cmd.go b/cmd.go index 8800c3d..3e37028 100644 --- a/cmd.go +++ b/cmd.go @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/subtle" "embed" "fmt" @@ -10,25 +11,23 @@ import ( "os" "strings" + "github.com/jackc/pgx/v5/pgxpool" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/spf13/cobra" "github.com/spf13/viper" ) -var ( - apiPort int - apiKey string -) - // apiCmd represents the api command var apiCmd = &cobra.Command{ Use: "api", Short: "Démarrer le serveur API", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - apiKey = viper.GetString(ViperAPIKey) - apiPort = viper.GetInt(ViperAPIPort) + var cfg Config + if err := viper.Unmarshal(&cfg); err != nil { + log.Fatal("parse config:", err) + } e := echo.New() @@ -36,30 +35,43 @@ var apiCmd = &cobra.Command{ e.Pre(middleware.AddTrailingSlash()) - if apiKey != "" { + if cfg.API.Key != "" { e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { - return subtle.ConstantTimeCompare([]byte(key), []byte(apiKey)) == 1, nil + return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.API.Key)) == 1, nil })) } // DataClient - /* - client, err := data.NewDataClientFromViper() - if err != nil { - log.Fatalf("Could not establish database connection.\n Error: %s\n", err) - } - defer client.DB.Close() + ctx := context.Background() - err = client.DB.Ping() - if err != nil { - log.Fatalf("Database was supposed to be ready but Ping() failed.\n Error: %s\n", err) - } + //prep + pool, err := pgxpool.New( + ctx, + fmt.Sprintf( + "user=%s password=%s database=%s host=%s port=%d sslmode=%s ", + cfg.DB.User, + cfg.DB.Password, + cfg.DB.Database, + cfg.DB.Host, + cfg.DB.Port, + cfg.DB.SSLMode, + )) + if err != nil { + log.Fatal("init pgx pool:", err) + } + defer pool.Close() - _, err = client.Seed() - if err != nil { - log.Fatalf("Error during client.Seed(): %s", err) - } - */ + db := &PostgresClient{ + Ctx: ctx, + Pool: pool, + } + if err := db.Pool.Ping(ctx); err != nil { + log.Fatal("ping db:", err) + } + + if err := db.CreateOrReplaceSchema(); err != nil { + log.Fatal("create or replace schema:", err) + } // Routes /* @@ -82,7 +94,7 @@ var apiCmd = &cobra.Command{ // Execution - e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", apiPort))) + e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.API.Port))) }, } @@ -187,16 +199,6 @@ func initConfig() { } } -var ( - webUser string - webPassword string - webPort int - webApiHost string - webApiKey string - webApiPort int - webApiProtocol string -) - //go:embed templates/* var templatesFS embed.FS @@ -214,13 +216,10 @@ var webCmd = &cobra.Command{ Short: "Démarrer le client web", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - webApiHost = viper.GetString(ViperWebAPIHost) - webApiKey = viper.GetString(ViperWebAPIKey) - webApiPort = viper.GetInt(ViperWebAPIPort) - webApiProtocol = viper.GetString(ViperWebAPIProtocol) - webPassword = viper.GetString(ViperWebPassword) - webPort = viper.GetInt(ViperWebPort) - webUser = viper.GetString(ViperWebUser) + var cfg Config + if err := viper.Unmarshal(&cfg); err != nil { + log.Fatal("init config:", err) + } // Ping API server /* @@ -244,8 +243,8 @@ var webCmd = &cobra.Command{ e.Pre(middleware.AddTrailingSlash()) e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { - usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(webUser)) == 1 - passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(webPassword)) == 1 + usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Web.User)) == 1 + passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Web.Password)) == 1 return usersMatch && passwordsMatch, nil })) @@ -268,13 +267,12 @@ var webCmd = &cobra.Command{ // Execution e.Logger.Fatal(e.Start( - fmt.Sprintf(":%d", webPort))) + fmt.Sprintf(":%d", cfg.Web.Port))) }, } func init() { rootCmd.AddCommand(webCmd) - //templatesFS = web.GetTemplates() // web.api.host webCmd.Flags().String(FlagWebAPIHost, DefaultWebAPIHost, DescriptionWebAPIHost) diff --git a/db_test.go b/db_test.go deleted file mode 100644 index 4c3881a..0000000 --- a/db_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "context" - "fmt" - "testing" - - "github.com/jackc/pgx/v5/pgxpool" - "github.com/spf13/viper" -) - -func TestDB(t *testing.T) { - cfg := DefaultConfig() - cfg.DB.Password = viper.GetString(ViperDBPassword) - - ctx := context.Background() - - //prep - pool, err := pgxpool.New( - ctx, - fmt.Sprintf( - "user=%s password=%s database=%s host=%s port=%d sslmode=%s ", - cfg.DB.User, - cfg.DB.Password, - cfg.DB.Database, - cfg.DB.Host, - cfg.DB.Port, - cfg.DB.SSLMode, - )) - if err != nil { - t.Error(err) - return - } - defer pool.Close() - - db := &PostgresClient{ - Ctx: ctx, - Pool: pool, - } - - //exec - - t.Run("create or replace schema", - func(t *testing.T) { - if err := db.CreateOrReplaceSchema(); err != nil { - t.Error(err) - } - }) -} From 780d493dc1e5ac431239771a7b7f92f9e115cfa7 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 6 Jun 2024 18:07:30 -0400 Subject: [PATCH 16/61] split cmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cmd.go contient maintenant juste les actual commandes. Les fonctionalités liées à la configuration sont dans config.go, et les fonctionalités liées au templating est dans template.go. --- cmd.go | 176 +++++----------------------------------------------- config.go | 143 +++++++++++++++++++++++++++++++++++++++++- main.go | 21 +++---- template.go | 20 ++++++ 4 files changed, 185 insertions(+), 175 deletions(-) create mode 100644 template.go diff --git a/cmd.go b/cmd.go index 3e37028..859f49a 100644 --- a/cmd.go +++ b/cmd.go @@ -3,13 +3,10 @@ package main import ( "context" "crypto/subtle" - "embed" "fmt" "html/template" - "io" "log" "os" - "strings" "github.com/jackc/pgx/v5/pgxpool" "github.com/labstack/echo/v4" @@ -18,6 +15,21 @@ import ( "github.com/spf13/viper" ) +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "bottin", + Short: "Bottin étudiant de l'AGECEM", +} + +// execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + // apiCmd represents the api command var apiCmd = &cobra.Command{ Use: "api", @@ -98,118 +110,6 @@ var apiCmd = &cobra.Command{ }, } -func init() { - rootCmd.AddCommand(apiCmd) - - // api.key - apiCmd.Flags().String(FlagAPIKey, DefaultAPIKey, DescriptionAPIKey) - if err := viper.BindPFlag(ViperAPIKey, apiCmd.Flags().Lookup(FlagAPIKey)); err != nil { - log.Fatal(err) - } - - // api.port - apiCmd.Flags().Int(FlagAPIPort, DefaultAPIPort, DescriptionAPIPort) - if err := viper.BindPFlag(ViperAPIPort, apiCmd.Flags().Lookup(FlagAPIPort)); err != nil { - log.Fatal(err) - } - - // db.database - apiCmd.Flags().String(FlagDBDatabase, DefaultDBDatabase, DescriptionDBDatabase) - if err := viper.BindPFlag(ViperDBDatabase, apiCmd.Flags().Lookup(FlagDBDatabase)); err != nil { - log.Fatal(err) - } - - // db.sslmode - apiCmd.Flags().String(FlagDBSSLMode, DefaultDBSSLMode, DescriptionDBSSLMode) - if err := viper.BindPFlag(ViperDBSSLMode, apiCmd.Flags().Lookup(FlagDBSSLMode)); err != nil { - log.Fatal(err) - } - - // db.host - apiCmd.Flags().String(FlagDBHost, DefaultDBHost, DescriptionDBHost) - if err := viper.BindPFlag(ViperDBHost, apiCmd.Flags().Lookup(FlagDBHost)); err != nil { - log.Fatal(err) - } - - // db.password - apiCmd.Flags().String(FlagDBPassword, DefaultDBPassword, DescriptionDBPassword) - if err := viper.BindPFlag(ViperDBPassword, apiCmd.Flags().Lookup(FlagDBPassword)); err != nil { - log.Fatal(err) - } - - // db.port - apiCmd.Flags().Int(FlagDBPort, DefaultDBPort, DescriptionDBPort) - if err := viper.BindPFlag(ViperDBPort, apiCmd.Flags().Lookup(FlagDBPort)); err != nil { - log.Fatal(err) - } - - // db.user - apiCmd.Flags().String(FlagDBUser, DefaultDBUser, DescriptionDBUser) - if err := viper.BindPFlag(ViperDBUser, apiCmd.Flags().Lookup(FlagDBUser)); err != nil { - log.Fatal(err) - } -} - -var cfgFile string - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "bottin", - Short: "Bottin étudiant de l'AGECEM", -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} - -func init() { - cobra.OnInitialize(initConfig) - - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") -} - -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - // Search config in home directory with name ".bottin" (without extension). - viper.AddConfigPath(home) - viper.SetConfigType("yaml") - viper.SetConfigName(".bottin") - } - - viper.SetEnvPrefix("BOTTIN") - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } -} - -//go:embed templates/* -var templatesFS embed.FS - -type Template struct { - templates *template.Template -} - -func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { - return t.templates.ExecuteTemplate(w, name, data) -} - // webCmd represents the web command var webCmd = &cobra.Command{ Use: "web", @@ -270,49 +170,3 @@ var webCmd = &cobra.Command{ fmt.Sprintf(":%d", cfg.Web.Port))) }, } - -func init() { - rootCmd.AddCommand(webCmd) - - // web.api.host - webCmd.Flags().String(FlagWebAPIHost, DefaultWebAPIHost, DescriptionWebAPIHost) - if err := viper.BindPFlag(ViperWebAPIHost, webCmd.Flags().Lookup(FlagWebAPIHost)); err != nil { - log.Fatal(err) - } - - // web.api.key - webCmd.Flags().String(FlagWebAPIKey, DefaultWebAPIKey, DescriptionWebAPIKey) - if err := viper.BindPFlag(ViperWebAPIKey, webCmd.Flags().Lookup(FlagWebAPIKey)); err != nil { - log.Fatal(err) - } - - // web.api.protocol - webCmd.Flags().String(FlagWebAPIProtocol, DefaultWebAPIProtocol, DescriptionWebAPIProtocol) - if err := viper.BindPFlag(ViperWebAPIProtocol, webCmd.Flags().Lookup(FlagWebAPIProtocol)); err != nil { - log.Fatal(err) - } - - // web.api.port - webCmd.Flags().Int(FlagWebAPIPort, DefaultWebAPIPort, DescriptionWebAPIPort) - if err := viper.BindPFlag(ViperWebAPIPort, webCmd.Flags().Lookup(FlagWebAPIPort)); err != nil { - log.Fatal(err) - } - - // web.password - webCmd.Flags().String(FlagWebPassword, DefaultWebPassword, DescriptionWebPassword) - if err := viper.BindPFlag(ViperWebPassword, webCmd.Flags().Lookup(FlagWebPassword)); err != nil { - log.Fatal(err) - } - - // web.port - webCmd.Flags().Int(FlagWebPort, DefaultWebPort, DescriptionWebPort) - if err := viper.BindPFlag(ViperWebPort, webCmd.Flags().Lookup(FlagWebPort)); err != nil { - log.Fatal(err) - } - - // web.user - webCmd.Flags().String(FlagWebUser, DefaultWebUser, DescriptionWebUser) - if err := viper.BindPFlag(ViperWebUser, webCmd.Flags().Lookup(FlagWebUser)); err != nil { - log.Fatal(err) - } -} diff --git a/config.go b/config.go index 73d6007..d50e241 100644 --- a/config.go +++ b/config.go @@ -1,6 +1,14 @@ package main -//TODO move flag declarations here +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) const ( ViperAPIPort string = "api.port" @@ -125,3 +133,136 @@ func DefaultConfig() (cfg Config) { cfg.Web.API.Protocol = DefaultWebAPIProtocol return } + +func init() { + // rootCmd + + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") + + // apiCmd + + rootCmd.AddCommand(apiCmd) + + // api.key + apiCmd.Flags().String(FlagAPIKey, DefaultAPIKey, DescriptionAPIKey) + if err := viper.BindPFlag(ViperAPIKey, apiCmd.Flags().Lookup(FlagAPIKey)); err != nil { + log.Fatal(err) + } + + // api.port + apiCmd.Flags().Int(FlagAPIPort, DefaultAPIPort, DescriptionAPIPort) + if err := viper.BindPFlag(ViperAPIPort, apiCmd.Flags().Lookup(FlagAPIPort)); err != nil { + log.Fatal(err) + } + + // db.database + apiCmd.Flags().String(FlagDBDatabase, DefaultDBDatabase, DescriptionDBDatabase) + if err := viper.BindPFlag(ViperDBDatabase, apiCmd.Flags().Lookup(FlagDBDatabase)); err != nil { + log.Fatal(err) + } + + // db.sslmode + apiCmd.Flags().String(FlagDBSSLMode, DefaultDBSSLMode, DescriptionDBSSLMode) + if err := viper.BindPFlag(ViperDBSSLMode, apiCmd.Flags().Lookup(FlagDBSSLMode)); err != nil { + log.Fatal(err) + } + + // db.host + apiCmd.Flags().String(FlagDBHost, DefaultDBHost, DescriptionDBHost) + if err := viper.BindPFlag(ViperDBHost, apiCmd.Flags().Lookup(FlagDBHost)); err != nil { + log.Fatal(err) + } + + // db.password + apiCmd.Flags().String(FlagDBPassword, DefaultDBPassword, DescriptionDBPassword) + if err := viper.BindPFlag(ViperDBPassword, apiCmd.Flags().Lookup(FlagDBPassword)); err != nil { + log.Fatal(err) + } + + // db.port + apiCmd.Flags().Int(FlagDBPort, DefaultDBPort, DescriptionDBPort) + if err := viper.BindPFlag(ViperDBPort, apiCmd.Flags().Lookup(FlagDBPort)); err != nil { + log.Fatal(err) + } + + // db.user + apiCmd.Flags().String(FlagDBUser, DefaultDBUser, DescriptionDBUser) + if err := viper.BindPFlag(ViperDBUser, apiCmd.Flags().Lookup(FlagDBUser)); err != nil { + log.Fatal(err) + } + + // WebCmd + rootCmd.AddCommand(webCmd) + + // web.api.host + webCmd.Flags().String(FlagWebAPIHost, DefaultWebAPIHost, DescriptionWebAPIHost) + if err := viper.BindPFlag(ViperWebAPIHost, webCmd.Flags().Lookup(FlagWebAPIHost)); err != nil { + log.Fatal(err) + } + + // web.api.key + webCmd.Flags().String(FlagWebAPIKey, DefaultWebAPIKey, DescriptionWebAPIKey) + if err := viper.BindPFlag(ViperWebAPIKey, webCmd.Flags().Lookup(FlagWebAPIKey)); err != nil { + log.Fatal(err) + } + + // web.api.protocol + webCmd.Flags().String(FlagWebAPIProtocol, DefaultWebAPIProtocol, DescriptionWebAPIProtocol) + if err := viper.BindPFlag(ViperWebAPIProtocol, webCmd.Flags().Lookup(FlagWebAPIProtocol)); err != nil { + log.Fatal(err) + } + + // web.api.port + webCmd.Flags().Int(FlagWebAPIPort, DefaultWebAPIPort, DescriptionWebAPIPort) + if err := viper.BindPFlag(ViperWebAPIPort, webCmd.Flags().Lookup(FlagWebAPIPort)); err != nil { + log.Fatal(err) + } + + // web.password + webCmd.Flags().String(FlagWebPassword, DefaultWebPassword, DescriptionWebPassword) + if err := viper.BindPFlag(ViperWebPassword, webCmd.Flags().Lookup(FlagWebPassword)); err != nil { + log.Fatal(err) + } + + // web.port + webCmd.Flags().Int(FlagWebPort, DefaultWebPort, DescriptionWebPort) + if err := viper.BindPFlag(ViperWebPort, webCmd.Flags().Lookup(FlagWebPort)); err != nil { + log.Fatal(err) + } + + // web.user + webCmd.Flags().String(FlagWebUser, DefaultWebUser, DescriptionWebUser) + if err := viper.BindPFlag(ViperWebUser, webCmd.Flags().Lookup(FlagWebUser)); err != nil { + log.Fatal(err) + } +} + +var cfgFile string + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".bottin" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".bottin") + } + + viper.SetEnvPrefix("BOTTIN") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/main.go b/main.go index 58be335..6f81738 100644 --- a/main.go +++ b/main.go @@ -1,23 +1,18 @@ package main -import ( - "context" - "fmt" - "io" -) - func main() { - //TODO - /* - if err := Run(context.Background(), Config{}, nil, os.Stdout); err != nil { - log.Fatal(err) - } + /* TODO + if err := Run(context.Background(), Config{}, nil, os.Stdout); err != nil { + log.Fatal(err) + } */ - // Handle the command-line - Execute() + // Handle the command-line via cobra and viper + execute() } +/* TODO func Run(ctx context.Context, config Config, args []string, stdout io.Writer) error { return fmt.Errorf("not implemented") } +*/ diff --git a/template.go b/template.go new file mode 100644 index 0000000..a5b4980 --- /dev/null +++ b/template.go @@ -0,0 +1,20 @@ +package main + +import ( + "embed" + "html/template" + "io" + + "github.com/labstack/echo/v4" +) + +//go:embed templates/* +var templatesFS embed.FS + +type Template struct { + templates *template.Template +} + +func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + return t.templates.ExecuteTemplate(w, name, data) +} From 11251042808e44bfa463e8beb893234edd31db0f Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Fri, 7 Jun 2024 14:59:49 -0400 Subject: [PATCH 17/61] chores: `go get -u` --- go.mod | 24 ++++++++++++------------ go.sum | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 1952064..a7833f1 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module git.agecem.com/agecem/bottin/v7 go 1.22.0 require ( - codeberg.org/vlbeaudoin/voki/v2 v2.0.3 + codeberg.org/vlbeaudoin/voki/v2 v2.1.0 github.com/jackc/pgx/v5 v5.6.0 - github.com/labstack/echo/v4 v4.11.4 + github.com/labstack/echo/v4 v4.12.0 github.com/spf13/cobra v1.8.0 - github.com/spf13/viper v1.18.2 + github.com/spf13/viper v1.19.0 ) require ( @@ -16,15 +16,15 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect @@ -34,12 +34,12 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 5c5e2b0..f022429 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ codeberg.org/vlbeaudoin/voki/v2 v2.0.3 h1:H3j7yk8uBiDK19OUWAKbYKmw0tsSw4t0LA5lyAfyT3E= codeberg.org/vlbeaudoin/voki/v2 v2.0.3/go.mod h1:TVdOLAxB94EJkylt5dleJlTkBzuxau8Xwd4TANQIR7U= +codeberg.org/vlbeaudoin/voki/v2 v2.1.0 h1:pXav77QGMHvMF1RyvkEwK3VKBdQh3ATmgh48TXX0tlU= +codeberg.org/vlbeaudoin/voki/v2 v2.1.0/go.mod h1:TVdOLAxB94EJkylt5dleJlTkBzuxau8Xwd4TANQIR7U= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -21,6 +23,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= @@ -31,6 +35,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -44,6 +50,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -52,6 +60,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -66,15 +76,19 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -85,18 +99,30 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 1b04237c96f29cd9373631b07ec79e49e9b9edb4 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Fri, 7 Jun 2024 15:18:22 -0400 Subject: [PATCH 18/61] =?UTF-8?q?ajouter=20fichiers=20manquants=20=C3=A0?= =?UTF-8?q?=20Dockerfile=20build=20step?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 361fe34..7c0ad26 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,9 +4,8 @@ LABEL author="vlbeaudoin" WORKDIR /go/src/app -COPY go.mod go.sum db.go entity.go main.go responses.go ./ +COPY go.mod go.sum cmd.go config.go db.go entity.go main.go responses.go template.go ./ -ADD cmd/ cmd/ ADD sql/ sql/ ADD templates/ templates/ From eca5ffa7fb061af5e320f397fc1cc1cfb71fd743 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Mon, 10 Jun 2024 17:25:01 -0400 Subject: [PATCH 19/61] feature(db): Ajouter InsertMembres, InsertProgrammes et GetMembres --- db.go | 244 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 120 insertions(+), 124 deletions(-) diff --git a/db.go b/db.go index fff8885..ff715d8 100644 --- a/db.go +++ b/db.go @@ -3,6 +3,7 @@ package main import ( "context" _ "embed" + "fmt" "github.com/jackc/pgx/v5/pgxpool" ) @@ -11,6 +12,7 @@ import ( var sqlSchema string type PostgresClient struct { + //TODO move context out of client Ctx context.Context Pool *pgxpool.Pool } @@ -20,142 +22,92 @@ func (db *PostgresClient) CreateOrReplaceSchema() error { return err } -/* -// DataClient is a postgres client based on sqlx -type DataClient struct { - PostgresConnection PostgresConnection - DB sqlx.DB -} - -type PostgresConnection struct { - User string - Password string - Database string - Host string - Port int - SSL bool -} - -func NewDataClientFromViper() (*DataClient, error) { - client, err := NewDataClient( - PostgresConnection{ - User: viper.GetString(cmd.ViperDBHost), - Password: viper.GetString(cmd.ViperDBPassword), - Host: viper.GetString(cmd.ViperDBHost), - Database: viper.GetString(cmd.ViperDBDatabase), - Port: viper.GetInt(cmd.ViperDBPort), - }) - - return client, err -} - -func NewDataClient(connection PostgresConnection) (*DataClient, error) { - client := &DataClient{PostgresConnection: connection} - - connectionString := fmt.Sprintf("postgres://%s:%s@%s:%d/%s", - client.PostgresConnection.User, - client.PostgresConnection.Password, - client.PostgresConnection.Host, - client.PostgresConnection.Port, - client.PostgresConnection.Database, - ) - - db, err := sqlx.Connect("pgx", connectionString) - if err != nil { - return nil, err - } - - client.DB = *db - - return client, nil -} - -func (d *DataClient) Seed() (int64, error) { - result, err := d.DB.Exec(sqlSchema) - if err != nil { - return 0, err - } - - rows, err := result.RowsAffected() - if err != nil { - return rows, err - } - - return rows, nil -} - // InsertMembres inserts a slice of Membre into a database, returning the amount inserted and any error encountered -func (d *DataClient) InsertMembres(membres []Membre) (int64, error) { - var rowsInserted int64 - tx, err := d.DB.Beginx() - if err != nil { - return rowsInserted, err - } - defer tx.Rollback() - - for _, membre := range membres { - if membre.ID == "" { - return 0, errors.New("Cannot insert membre with no membre_id") - } - result, err := tx.NamedExec("INSERT INTO membres (id, last_name, first_name, prefered_name, programme_id) VALUES (:id, :last_name, :first_name, :prefered_name, :programme_id) ON CONFLICT (id) DO NOTHING;", &membre) +func (d *PostgresClient) InsertMembres(membres []Membre) (inserted int64, err error) { + select { + case <-d.Ctx.Done(): + return inserted, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) + default: + tx, err := d.Pool.Begin(d.Ctx) if err != nil { + return inserted, err + } + defer tx.Rollback(d.Ctx) + + for i, membre := range membres { + if membre.ID == "" { + return inserted, fmt.Errorf("insertion ligne %d: membre requiert numéro étudiant valide", i) + } + + result, err := tx.Exec(d.Ctx, ` +INSERT INTO membres + (id, last_name, first_name, prefered_name, programme_id) +VALUES + ($1, $2, $3, $4, $5) +ON CONFLICT (id) DO NOTHING;`, + membre.ID, + membre.LastName, + membre.FirstName, + membre.PreferedName, + membre.ProgrammeID, + ) + if err != nil { + return 0, err + } + + inserted += result.RowsAffected() + } + + if err = tx.Commit(d.Ctx); err != nil { return 0, err } - rows, err := result.RowsAffected() - if err != nil { - return 0, err - } - - rowsInserted += rows + return inserted, err } - - err = tx.Commit() - if err != nil { - return rowsInserted, err - } - - return rowsInserted, nil } -func (d *DataClient) InsertProgrammes(programmes []Programme) (int64, error) { - var rowsInserted int64 - tx, err := d.DB.Beginx() - if err != nil { - return rowsInserted, err - } - defer tx.Rollback() - - for _, programme := range programmes { - if programme.ID == "" { - return 0, errors.New("Cannot insert programme with no programme_id") - } - - result, err := tx.NamedExec("INSERT INTO programmes (id, titre) VALUES (:id, :titre) ON CONFLICT DO NOTHING;", &programme) +func (d *PostgresClient) InsertProgrammes(programmes []Programme) (inserted int64, err error) { + select { + case <-d.Ctx.Done(): + return inserted, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) + default: + tx, err := d.Pool.Begin(d.Ctx) if err != nil { - return 0, err + return inserted, err + } + defer tx.Rollback(d.Ctx) + + for _, programme := range programmes { + if programme.ID == "" { + return 0, fmt.Errorf("Cannot insert programme with no programme_id") + } + + result, err := tx.Exec(d.Ctx, ` +INSERT INTO programmes +(id, titre) +VALUES ($1, $2) ON CONFLICT DO NOTHING;`, + programme.ID, + programme.Titre) + if err != nil { + return 0, err + } + + inserted += result.RowsAffected() } - rows, err := result.RowsAffected() - if err != nil { - return 0, err + if err := tx.Commit(d.Ctx); err != nil { + return inserted, err } - rowsInserted += rows + return inserted, err } - - err = tx.Commit() - if err != nil { - return rowsInserted, err - } - - return rowsInserted, nil } -func (d *DataClient) GetMembre(membreID string) (Membre, error) { +/* +func (d *PostgresClient) GetMembre(membreID string) (Membre, error) { var membre Membre - rows, err := d.DB.Queryx("SELECT * FROM membres WHERE id = $1 LIMIT 1;", membreID) + rows, err := d.Pool.Queryx("SELECT * FROM membres WHERE id = $1 LIMIT 1;", membreID) if err != nil { return membre, err } @@ -173,9 +125,11 @@ func (d *DataClient) GetMembre(membreID string) (Membre, error) { return membre, nil } +*/ -func (d *DataClient) UpdateMembreName(membreID, newName string) (int64, error) { - result, err := d.DB.Exec("UPDATE membres SET prefered_name = $1 WHERE id = $2;", newName, membreID) +/* +func (d *PostgresClient) UpdateMembreName(membreID, newName string) (int64, error) { + result, err := d.Pool.Exec("UPDATE membres SET prefered_name = $1 WHERE id = $2;", newName, membreID) if err != nil { return 0, err } @@ -187,8 +141,50 @@ func (d *DataClient) UpdateMembreName(membreID, newName string) (int64, error) { return rows, nil } - -func (d *DataClient) GetMembres() (membres []Membre, err error) { - return membres, d.DB.Select(&membres, "SELECT * FROM membres;") -} */ + +func (d *PostgresClient) GetMembres() (membres []Membre, err error) { + select { + case <-d.Ctx.Done(): + return nil, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) + default: + rows, err := d.Pool.Query(d.Ctx, ` +SELECT + membres.id, + membres.last_name, + membres.first_name, + membres.prefered_name, + membres.programme_id +FROM + membres +LIMIT + 10000 +ORDER BY + membres.id;`) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var membre Membre + + if err = rows.Scan( + &membre.ID, + &membre.LastName, + &membre.FirstName, + &membre.PreferedName, + &membre.ProgrammeID, + ); err != nil { + return nil, err + } + + membres = append(membres, membre) + } + if rows.Err() != nil { + return membres, rows.Err() + } + + return membres, nil + } +} From be766f593db1f1af072d99f607248967756433f7 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 11 Jun 2024 17:28:20 -0400 Subject: [PATCH 20/61] ajouter API client et tester /api/health --- client.go | 25 +++++++++++++++++++++ client_test.go | 39 +++++++++++++++++++++++++++++++- cmd.go | 4 +++- go.mod | 3 ++- go.sum | 36 ++++++----------------------- request.go | 40 +++++++++++++++++++++++++++++++++ response.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ responses.go | 28 ----------------------- routes.go | 45 +++++++++++++++++++++++++++++++++++++ 9 files changed, 221 insertions(+), 60 deletions(-) create mode 100644 request.go create mode 100644 response.go delete mode 100644 responses.go create mode 100644 routes.go diff --git a/client.go b/client.go index 06ab7d0..918fbee 100644 --- a/client.go +++ b/client.go @@ -1 +1,26 @@ package main + +import ( + "fmt" + + "codeberg.org/vlbeaudoin/voki/v3" +) + +type APIClient struct { + Voki *voki.Voki +} + +func (c APIClient) GetHealth() (health string, err error) { + var request HealthGETRequest + response, err := request.Request(c.Voki) + if err != nil { + return "", err + } + + if code, message := response.StatusCode(), response.Message; code >= 400 { + err = fmt.Errorf("%d: %s", code, message) + return + } + + return response.Message, nil +} diff --git a/client_test.go b/client_test.go index 0fee6f5..2c66470 100644 --- a/client_test.go +++ b/client_test.go @@ -1 +1,38 @@ -package main_test +package main + +import ( + "net/http" + "testing" + + "codeberg.org/vlbeaudoin/voki/v3" + "github.com/spf13/viper" +) + +func TestAPI(t *testing.T) { + var cfg Config + if err := viper.Unmarshal(&cfg); err != nil { + t.Error(err) + return + } + + httpClient := http.DefaultClient + defer httpClient.CloseIdleConnections() + + vokiClient := voki.New(httpClient, "localhost", cfg.API.Key, cfg.API.Port, "http") + apiClient := APIClient{vokiClient} + + t.Run("get API health", func(t *testing.T) { + health, err := apiClient.GetHealth() + if err != nil { + t.Error(err) + } + + want := "ok" + got := health + + if want != got { + t.Errorf("want=%s got=%s", want, got) + } + }) + +} diff --git a/cmd.go b/cmd.go index 859f49a..4cd12fb 100644 --- a/cmd.go +++ b/cmd.go @@ -86,6 +86,9 @@ var apiCmd = &cobra.Command{ } // Routes + if err := addRoutes(e, db); err != nil { + log.Fatal("add routes:", err) + } /* h := handlers.New(client) @@ -105,7 +108,6 @@ var apiCmd = &cobra.Command{ */ // Execution - e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.API.Port))) }, } diff --git a/go.mod b/go.mod index a7833f1..b6ce20d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module git.agecem.com/agecem/bottin/v7 go 1.22.0 require ( - codeberg.org/vlbeaudoin/voki/v2 v2.1.0 + codeberg.org/vlbeaudoin/pave/v2 v2.0.0 + codeberg.org/vlbeaudoin/voki/v3 v3.0.0 github.com/jackc/pgx/v5 v5.6.0 github.com/labstack/echo/v4 v4.12.0 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index f022429..73ace91 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -codeberg.org/vlbeaudoin/voki/v2 v2.0.3 h1:H3j7yk8uBiDK19OUWAKbYKmw0tsSw4t0LA5lyAfyT3E= -codeberg.org/vlbeaudoin/voki/v2 v2.0.3/go.mod h1:TVdOLAxB94EJkylt5dleJlTkBzuxau8Xwd4TANQIR7U= -codeberg.org/vlbeaudoin/voki/v2 v2.1.0 h1:pXav77QGMHvMF1RyvkEwK3VKBdQh3ATmgh48TXX0tlU= -codeberg.org/vlbeaudoin/voki/v2 v2.1.0/go.mod h1:TVdOLAxB94EJkylt5dleJlTkBzuxau8Xwd4TANQIR7U= +codeberg.org/vlbeaudoin/pave/v2 v2.0.0 h1:hfB5KnqMMu17g5QBWgLvWOsqidrYaohRfu2LflmTrb0= +codeberg.org/vlbeaudoin/pave/v2 v2.0.0/go.mod h1:TsTfP6IA+3Ph33vLZigeJWS5vgBPgkW1tfs3zFPfycU= +codeberg.org/vlbeaudoin/voki/v3 v3.0.0 h1:XdF/UTe9YUNj3hYrAyEvdmIMDYLL8SkqTwPkqw1yJ2c= +codeberg.org/vlbeaudoin/voki/v3 v3.0.0/go.mod h1:+6LMXosAu2ijNKV04sMwkeujpH+cghZU1fydqj2y95g= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,16 +13,14 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= @@ -33,8 +31,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= -github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -48,8 +44,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -58,8 +52,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -74,8 +66,6 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -86,8 +76,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= @@ -97,30 +87,18 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= diff --git a/request.go b/request.go new file mode 100644 index 0000000..ecb4346 --- /dev/null +++ b/request.go @@ -0,0 +1,40 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + + "codeberg.org/vlbeaudoin/voki/v3" +) + +var _ voki.Requester[HealthGETResponse] = HealthGETRequest{} + +type HealthGETRequest struct{} + +func (request HealthGETRequest) Complete() bool { return true } + +func (request HealthGETRequest) Request(v *voki.Voki) (response HealthGETResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete HealthGET request") + return + } + + statusCode, body, err := v.CallAndParse( + http.MethodGet, + "/api/health/", + nil, + true, + ) + if err != nil { + err = fmt.Errorf("%d: %s", statusCode, err) + return + } + response.SetStatusCode(statusCode) + + if err = json.Unmarshal(body, &response); err != nil { + return + } + + return +} diff --git a/response.go b/response.go new file mode 100644 index 0000000..55a700d --- /dev/null +++ b/response.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + + "codeberg.org/vlbeaudoin/voki/v3" +) + +type APIResponse struct { + voki.MessageResponse + statusCode int +} + +func (R APIResponse) StatusCode() int { return R.statusCode } + +func (R *APIResponse) SetStatusCode(code int) error { + if code <= 0 { + return fmt.Errorf("Cannot set status code to %d", code) + } + R.statusCode = code + return nil +} + +type HealthGETResponse struct { + APIResponse +} + +type MembreGETResponse struct { + APIResponse + Data MembreGETResponseData `json:"data"` +} +type MembreGETResponseData struct { + Membre Membre `json:"membre"` +} + +type MembresGETResponse struct { + APIResponse + Data MembresGETResponseData `json:"data"` +} + +type MembresGETResponseData struct { + Membres []Membre `json:"membres"` +} + +type MembresPOSTResponse struct { + APIResponse + Data MembresPOSTResponseData `json:"data"` +} + +type MembresPOSTResponseData struct { + MembresInserted int64 `json:"membres_inserted"` +} + +type ProgrammesPOSTResponse struct { + APIResponse + Data ProgrammesPOSTResponseData `json:"data"` +} + +type ProgrammesPOSTResponseData struct { + ProgrammesInserted int64 `json:"programmes_inserted"` +} diff --git a/responses.go b/responses.go deleted file mode 100644 index d8af880..0000000 --- a/responses.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import "codeberg.org/vlbeaudoin/voki/v2" - -type GetHealthResponse struct { - voki.ResponseWithError -} - -type ListMembresResponse struct { - voki.ResponseWithError - Data struct { - Membres []Membre - } -} - -type PostMembresResponse struct { - voki.ResponseWithError - Data struct { - MembresInserted int64 - } -} - -type PostProgrammesResponse struct { - voki.ResponseWithError - Data struct { - ProgrammesInserted int64 - } -} diff --git a/routes.go b/routes.go new file mode 100644 index 0000000..ee07763 --- /dev/null +++ b/routes.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "net/http" + + "codeberg.org/vlbeaudoin/pave/v2" + "codeberg.org/vlbeaudoin/voki/v3" + "github.com/labstack/echo/v4" +) + +func addRoutes(e *echo.Echo, db *PostgresClient) error { + _ = db + + apiPath := "/api" + apiGroup := e.Group(apiPath) + p := pave.New() + if err := pave.EchoRegister[HealthGETRequest]( + apiGroup, + &p, + apiPath, + http.MethodGet, + "/health/", + "Get API server health", + "HealthGET", func(c echo.Context) error { + var request, response = HealthGETRequest{}, HealthGETResponse{} + if !request.Complete() { + var response voki.ResponseBadRequest + response.Message = "Incomplete HealthGET request received" + return c.JSON(response.StatusCode(), response) + } + if err := response.SetStatusCode(http.StatusOK); err != nil { + var response voki.ResponseInternalServerError + response.Message = fmt.Sprintf("handler: %s", err) + return c.JSON(response.StatusCode(), response) + } + + response.Message = "ok" + return c.JSON(response.StatusCode(), response) + + }); err != nil { + return err + } + return nil +} From c5339bd45b76a0458e916368421bc36cd241e33d Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Mon, 17 Jun 2024 14:06:43 -0400 Subject: [PATCH 21/61] fix(Dockerfile): copier fichiers go manquants vers image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7c0ad26..6479581 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ LABEL author="vlbeaudoin" WORKDIR /go/src/app -COPY go.mod go.sum cmd.go config.go db.go entity.go main.go responses.go template.go ./ +COPY go.mod go.sum client.go client_test.go cmd.go config.go db.go entity.go main.go request.go response.go routes.go template.go ./ ADD sql/ sql/ ADD templates/ templates/ From e1bce94d18e78788ae3be1f3fd0061ef8eb6d103 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Mon, 17 Jun 2024 14:07:49 -0400 Subject: [PATCH 22/61] feature: add and test ProgrammesPOST --- client.go | 17 +++++++++++++++++ client_test.go | 23 +++++++++++++++++++++++ db.go | 2 +- request.go | 41 +++++++++++++++++++++++++++++++++++++++++ routes.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index 918fbee..dd7e1ea 100644 --- a/client.go +++ b/client.go @@ -24,3 +24,20 @@ func (c APIClient) GetHealth() (health string, err error) { return response.Message, nil } + +func (c APIClient) InsertProgrammes(programmes ...Programme) (amountInserted int64, err error) { + var request ProgrammesPOSTRequest + request.Data.Programmes = programmes + + response, err := request.Request(c.Voki) + if err != nil { + return + } + + if code, message := response.StatusCode(), response.Message; code >= 400 { + err = fmt.Errorf("%d: %s", code, message) + return + } + + return response.Data.ProgrammesInserted, nil +} diff --git a/client_test.go b/client_test.go index 2c66470..1f902d2 100644 --- a/client_test.go +++ b/client_test.go @@ -8,6 +8,10 @@ import ( "github.com/spf13/viper" ) +func init() { + initConfig() +} + func TestAPI(t *testing.T) { var cfg Config if err := viper.Unmarshal(&cfg); err != nil { @@ -35,4 +39,23 @@ func TestAPI(t *testing.T) { } }) + //TODO create or replace schema + //TODO insert programmes + t.Run("insert programmes", + func(t *testing.T) { + programmes := []Programme{ + {"404.42", "Cool programme"}, + {"200.10", "Autre programme"}, + } + t.Log("programmes:", programmes) + _, err := apiClient.InsertProgrammes(programmes...) + if err != nil { + t.Error(err) + } + }) + //TODO insert membres + //TODO get membre + //TODO update membre prefered name + //TODO get membres + } diff --git a/db.go b/db.go index ff715d8..eda594c 100644 --- a/db.go +++ b/db.go @@ -66,7 +66,7 @@ ON CONFLICT (id) DO NOTHING;`, } } -func (d *PostgresClient) InsertProgrammes(programmes []Programme) (inserted int64, err error) { +func (d *PostgresClient) InsertProgrammes(programmes ...Programme) (inserted int64, err error) { select { case <-d.Ctx.Done(): return inserted, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) diff --git a/request.go b/request.go index ecb4346..3ff29d7 100644 --- a/request.go +++ b/request.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "encoding/json" "fmt" "net/http" @@ -38,3 +39,43 @@ func (request HealthGETRequest) Request(v *voki.Voki) (response HealthGETRespons return } + +var _ voki.Requester[ProgrammesPOSTResponse] = ProgrammesPOSTRequest{} + +type ProgrammesPOSTRequest struct { + Data struct { + Programmes []Programme + } +} + +func (request ProgrammesPOSTRequest) Complete() bool { + return len(request.Data.Programmes) != 0 +} +func (request ProgrammesPOSTRequest) Request(v *voki.Voki) (response ProgrammesPOSTResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete ProgrammesPOSTRequest") + return + } + + var buf bytes.Buffer + if err = json.NewEncoder(&buf).Encode(request.Data); err != nil { + return + } + + statusCode, body, err := v.CallAndParse( + http.MethodPost, + "/api/programmes/", + &buf, + true, + ) + if err != nil { + err = fmt.Errorf("code=%d err=%s", statusCode, err) + return + } + response.SetStatusCode(statusCode) + if err = json.Unmarshal(body, &response); err != nil { + return + } + + return +} diff --git a/routes.go b/routes.go index ee07763..6041358 100644 --- a/routes.go +++ b/routes.go @@ -41,5 +41,48 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { }); err != nil { return err } + + if err := pave.EchoRegister[ProgrammesPOSTRequest]( + apiGroup, + &p, + apiPath, + http.MethodPost, + "/programmes/", + "Get registered programmes", + "ProgrammesPOST", func(c echo.Context) error { + var request, response = ProgrammesPOSTRequest{}, ProgrammesPOSTResponse{} + + if err := c.Bind(&request.Data); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse request body: %s", err) + return c.JSON(response.StatusCode(), response) + } + + if !request.Complete() { + var response voki.ResponseBadRequest + response.Message = "Incomplete ProgrammesPOST request received" + return c.JSON(response.StatusCode(), response) + } + + amountInserted, err := db.InsertProgrammes(request.Data.Programmes...) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("db: %s", err) + return c.JSON(response.StatusCode(), response) + } + response.Data.ProgrammesInserted = amountInserted + + if err := response.SetStatusCode(http.StatusOK); err != nil { + var response voki.ResponseInternalServerError + response.Message = fmt.Sprintf("handler: %s", err) + return c.JSON(response.StatusCode(), response) + } + + response.Message = "ok" + return c.JSON(response.StatusCode(), response) + + }); err != nil { + return err + } return nil } From e847f693e057a2652ff019f5fc15d7cdacce9d6f Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Mon, 17 Jun 2024 17:25:53 -0400 Subject: [PATCH 23/61] rework: renommer champs dans entities et ajouter MembresPOST - ajouter et tester InsertMembres - ajouter sql/views.sql - ajouter view `membres_for_display` -> concat names ou prefered name - rendre plusieurs champs NOT NULL dans schema --- client.go | 17 +++++++++++++++++ client_test.go | 23 ++++++++++++++++++++++- cmd.go | 4 ++++ db.go | 14 +++++++++++--- entity.go | 4 ++-- request.go | 41 +++++++++++++++++++++++++++++++++++++++++ routes.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- sql/schema.sql | 8 ++++---- sql/views.sql | 23 +++++++++++++++++++++++ 9 files changed, 168 insertions(+), 11 deletions(-) create mode 100644 sql/views.sql diff --git a/client.go b/client.go index dd7e1ea..6221ce0 100644 --- a/client.go +++ b/client.go @@ -41,3 +41,20 @@ func (c APIClient) InsertProgrammes(programmes ...Programme) (amountInserted int return response.Data.ProgrammesInserted, nil } + +func (c APIClient) InsertMembres(membres ...Membre) (amountInserted int64, err error) { + var request MembresPOSTRequest + request.Data.Membres = membres + + response, err := request.Request(c.Voki) + if err != nil { + return + } + + if code, message := response.StatusCode(), response.Message; code >= 400 { + err = fmt.Errorf("%d: %s", code, message) + return + } + + return response.Data.MembresInserted, nil +} diff --git a/client_test.go b/client_test.go index 1f902d2..9d1212d 100644 --- a/client_test.go +++ b/client_test.go @@ -40,7 +40,6 @@ func TestAPI(t *testing.T) { }) //TODO create or replace schema - //TODO insert programmes t.Run("insert programmes", func(t *testing.T) { programmes := []Programme{ @@ -54,6 +53,28 @@ func TestAPI(t *testing.T) { } }) //TODO insert membres + t.Run("insert membres", + func(t *testing.T) { + membres := []Membre{ + { + ID: "0000000", + FirstName: "Test", + LastName: "User", + ProgrammeID: "404.42", + }, + { + ID: "1234567", + FirstName: "Deadname", + LastName: "User", + PreferedName: "User, Test-Name", + ProgrammeID: "200.10", + }, + } + _, err := apiClient.InsertMembres(membres...) + if err != nil { + t.Error(err) + } + }) //TODO get membre //TODO update membre prefered name //TODO get membres diff --git a/cmd.go b/cmd.go index 4cd12fb..6067795 100644 --- a/cmd.go +++ b/cmd.go @@ -85,6 +85,10 @@ var apiCmd = &cobra.Command{ log.Fatal("create or replace schema:", err) } + if err := db.CreateOrReplaceViews(); err != nil { + log.Fatal("create or replace views:", err) + } + // Routes if err := addRoutes(e, db); err != nil { log.Fatal("add routes:", err) diff --git a/db.go b/db.go index eda594c..c2394e4 100644 --- a/db.go +++ b/db.go @@ -11,6 +11,9 @@ import ( //go:embed sql/schema.sql var sqlSchema string +//go:embed sql/views.sql +var sqlViews string + type PostgresClient struct { //TODO move context out of client Ctx context.Context @@ -22,8 +25,13 @@ func (db *PostgresClient) CreateOrReplaceSchema() error { return err } +func (db *PostgresClient) CreateOrReplaceViews() error { + _, err := db.Pool.Exec(db.Ctx, sqlViews) + return err +} + // InsertMembres inserts a slice of Membre into a database, returning the amount inserted and any error encountered -func (d *PostgresClient) InsertMembres(membres []Membre) (inserted int64, err error) { +func (d *PostgresClient) InsertMembres(membres ...Membre) (inserted int64, err error) { select { case <-d.Ctx.Done(): return inserted, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) @@ -84,10 +92,10 @@ func (d *PostgresClient) InsertProgrammes(programmes ...Programme) (inserted int result, err := tx.Exec(d.Ctx, ` INSERT INTO programmes -(id, titre) +(id, name) VALUES ($1, $2) ON CONFLICT DO NOTHING;`, programme.ID, - programme.Titre) + programme.Name) if err != nil { return 0, err } diff --git a/entity.go b/entity.go index 52e54d0..385acde 100644 --- a/entity.go +++ b/entity.go @@ -1,8 +1,8 @@ package main type Programme struct { - ID string `db:"id" json:"programme_id" csv:"programme_id"` - Titre string `db:"titre" json:"nom_programme" csv:"nom_programme"` + ID string `db:"id" json:"programme_id" csv:"programme_id"` + Name string `db:"name" json:"nom_programme" csv:"nom_programme"` } type Membre struct { diff --git a/request.go b/request.go index 3ff29d7..720561b 100644 --- a/request.go +++ b/request.go @@ -79,3 +79,44 @@ func (request ProgrammesPOSTRequest) Request(v *voki.Voki) (response ProgrammesP return } + +var _ voki.Requester[MembresPOSTResponse] = MembresPOSTRequest{} + +type MembresPOSTRequest struct { + Data struct { + Membres []Membre + } +} + +func (request MembresPOSTRequest) Complete() bool { + return len(request.Data.Membres) != 0 +} + +func (request MembresPOSTRequest) Request(v *voki.Voki) (response MembresPOSTResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete MembresPOSTRequest") + return + } + + var buf bytes.Buffer + if err = json.NewEncoder(&buf).Encode(request.Data); err != nil { + return + } + + statusCode, body, err := v.CallAndParse( + http.MethodPost, + "/api/membres/", + &buf, + true, + ) + if err != nil { + err = fmt.Errorf("code=%d err=%s", statusCode, err) + return + } + response.SetStatusCode(statusCode) + if err = json.Unmarshal(body, &response); err != nil { + return + } + + return +} diff --git a/routes.go b/routes.go index 6041358..05c62b5 100644 --- a/routes.go +++ b/routes.go @@ -48,7 +48,7 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { apiPath, http.MethodPost, "/programmes/", - "Get registered programmes", + "Insert programmes", "ProgrammesPOST", func(c echo.Context) error { var request, response = ProgrammesPOSTRequest{}, ProgrammesPOSTResponse{} @@ -84,5 +84,48 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { }); err != nil { return err } + + if err := pave.EchoRegister[MembresPOSTRequest]( + apiGroup, + &p, + apiPath, + http.MethodPost, + "/membres/", + "Insert membres", + "MembresPOST", func(c echo.Context) error { + var request, response = MembresPOSTRequest{}, MembresPOSTResponse{} + + if err := c.Bind(&request.Data); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse request body: %s", err) + return c.JSON(response.StatusCode(), response) + } + + if !request.Complete() { + var response voki.ResponseBadRequest + response.Message = "Incomplete MembresPOST request received" + return c.JSON(response.StatusCode(), response) + } + + amountInserted, err := db.InsertMembres(request.Data.Membres...) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("db: %s", err) + return c.JSON(response.StatusCode(), response) + } + response.Data.MembresInserted = amountInserted + + if err := response.SetStatusCode(http.StatusOK); err != nil { + var response voki.ResponseInternalServerError + response.Message = fmt.Sprintf("handler: %s", err) + return c.JSON(response.StatusCode(), response) + } + + response.Message = "ok" + return c.JSON(response.StatusCode(), response) + + }); err != nil { + return err + } return nil } diff --git a/sql/schema.sql b/sql/schema.sql index 154b983..6253131 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -1,12 +1,12 @@ CREATE TABLE IF NOT EXISTS programmes ( id TEXT PRIMARY KEY, - titre TEXT + name TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS membres ( id VARCHAR(7) PRIMARY KEY, - last_name TEXT, - first_name TEXT, + last_name TEXT NOT NULL, + first_name TEXT NOT NULL, prefered_name TEXT, - programme_id TEXT REFERENCES programmes(id) + programme_id TEXT REFERENCES programmes(id) NOT NULL ); diff --git a/sql/views.sql b/sql/views.sql new file mode 100644 index 0000000..ecdde1a --- /dev/null +++ b/sql/views.sql @@ -0,0 +1,23 @@ +-- membres_for_display affiche le numéro étudiant, nom complet OU prefered_name, et titre du programme. +-- +-- Utilisé par l'application web pour rechercher et afficher les informations des membres +CREATE OR REPLACE VIEW + "membres_for_display" +AS ( + SELECT + "membres".id, + CASE + WHEN + "membres".prefered_name != '' AND "membres".prefered_name IS NOT NULL + THEN + "membres".prefered_name + ELSE + CONCAT("membres".last_name, ', ', "membres".first_name) + END AS name, + "programmes".id AS programme_id, + "programmes".name AS programme_name + FROM + "membres" + INNER JOIN + "programmes" ON "programmes".id = "membres".programme_id +); From c7c64674c771a49c58233c6d608d14355a508c0b Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 18 Jun 2024 19:44:20 -0400 Subject: [PATCH 24/61] rework: change api prefix to /api/v7/ - add and test GetMembre - add `IsMembreID(string) bool` function BREAKING: Rename routes to `/api/v7/...` scheme --- client.go | 17 +++++++++++ client_test.go | 81 +++++++++++++++++++++++++++++++++++++------------- db.go | 51 +++++++++++++++++++------------ entity.go | 16 ++++++++++ request.go | 47 +++++++++++++++++++++++++++-- routes.go | 45 ++++++++++++++++++++++++++-- 6 files changed, 212 insertions(+), 45 deletions(-) diff --git a/client.go b/client.go index 6221ce0..d47a723 100644 --- a/client.go +++ b/client.go @@ -58,3 +58,20 @@ func (c APIClient) InsertMembres(membres ...Membre) (amountInserted int64, err e return response.Data.MembresInserted, nil } + +func (c APIClient) GetMembre(membreID string) (membre Membre, err error) { + var request MembreGETRequest + request.Param.MembreID = membreID + + response, err := request.Request(c.Voki) + if err != nil { + return + } + + if code, message := response.StatusCode(), response.Message; code >= 400 { + err = fmt.Errorf("%d: %s", code, message) + return + } + + return response.Data.Membre, nil +} diff --git a/client_test.go b/client_test.go index 9d1212d..7a847ad 100644 --- a/client_test.go +++ b/client_test.go @@ -40,6 +40,7 @@ func TestAPI(t *testing.T) { }) //TODO create or replace schema + t.Run("insert programmes", func(t *testing.T) { programmes := []Programme{ @@ -52,31 +53,71 @@ func TestAPI(t *testing.T) { t.Error(err) } }) - //TODO insert membres + + testMembres := []Membre{ + { + ID: "0000000", + FirstName: "Test", + LastName: "User", + ProgrammeID: "404.42", + }, + { + ID: "1234567", + FirstName: "Deadname", + LastName: "User", + PreferedName: "User, Test-Name", + ProgrammeID: "200.10", + }, + } + t.Run("insert membres", func(t *testing.T) { - membres := []Membre{ - { - ID: "0000000", - FirstName: "Test", - LastName: "User", - ProgrammeID: "404.42", - }, - { - ID: "1234567", - FirstName: "Deadname", - LastName: "User", - PreferedName: "User, Test-Name", - ProgrammeID: "200.10", - }, - } - _, err := apiClient.InsertMembres(membres...) + _, err := apiClient.InsertMembres(testMembres...) if err != nil { t.Error(err) } }) - //TODO get membre - //TODO update membre prefered name - //TODO get membres + t.Run("get membre", + func(t *testing.T) { + membre, err := apiClient.GetMembre(testMembres[0].ID) + if err != nil { + t.Error(err) + } + + want := testMembres[0].LastName + got := membre.LastName + + if want != got { + t.Errorf("want=%s got=%s", want, got) + } + }) + + t.Run("get invalid membre", + func(t *testing.T) { + _, err := apiClient.GetMembre("invalid") + if err == nil { + t.Error("`invalid` should not have been accepted as value to GetMembre, but did") + } + }) + + //TODO update membre prefered name + /* + t.Run("", + func(t *testing.T) { + if err := apiClient.UpdateMembrePreferedName(testMembres[0].ID, "User, Galaxy"); err != nil { + t.Error(err) + } + }) + */ + //TODO get membres + /* + t.Run("get membres, max 50", + func(t *testing.T) { + membres, err := apiClient.GetMembres(50) + if err != nil { + t.Error(err) + } + }) + */ } diff --git a/db.go b/db.go index c2394e4..bb401ae 100644 --- a/db.go +++ b/db.go @@ -111,29 +111,42 @@ VALUES ($1, $2) ON CONFLICT DO NOTHING;`, } } -/* -func (d *PostgresClient) GetMembre(membreID string) (Membre, error) { - var membre Membre - - rows, err := d.Pool.Queryx("SELECT * FROM membres WHERE id = $1 LIMIT 1;", membreID) - if err != nil { - return membre, err - } - - for rows.Next() { - err := rows.StructScan(&membre) - if err != nil { - return membre, err +func (d *PostgresClient) GetMembre(membreID string) (membre Membre, err error) { + select { + case <-d.Ctx.Done(): + err = fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) + return + default: + if err = d.Pool.QueryRow(d.Ctx, ` +SELECT + "membres".id, + "membres".last_name, + "membres".first_name, + "membres".prefered_name, + "membres".programme_id +FROM + "membres" +WHERE + "membres".id = $1 +LIMIT + 1; +`, membreID).Scan( + &membre.ID, + &membre.LastName, + &membre.FirstName, + &membre.PreferedName, + &membre.ProgrammeID, + ); err != nil { + return } - } - if membre.ID == "" { - return membre, fmt.Errorf("No membre by that id was found") - } + if membre.ID == "" { + return membre, fmt.Errorf("Aucun membre trouvé avec numéro '%s'", membre.ID) + } - return membre, nil + return membre, nil + } } -*/ /* func (d *PostgresClient) UpdateMembreName(membreID, newName string) (int64, error) { diff --git a/entity.go b/entity.go index 385acde..b919c29 100644 --- a/entity.go +++ b/entity.go @@ -1,5 +1,7 @@ package main +import "unicode" + type Programme struct { ID string `db:"id" json:"programme_id" csv:"programme_id"` Name string `db:"name" json:"nom_programme" csv:"nom_programme"` @@ -12,3 +14,17 @@ type Membre struct { PreferedName string `db:"prefered_name" json:"prefered_name" csv:"prefered_name"` ProgrammeID string `db:"programme_id" json:"programme_id" csv:"programme_id"` } + +func IsMembreID(membre_id string) bool { + if len(membre_id) != 7 { + return false + } + + for _, character := range membre_id { + if !unicode.IsDigit(character) { + return false + } + } + + return true +} diff --git a/request.go b/request.go index 720561b..277e6f4 100644 --- a/request.go +++ b/request.go @@ -23,7 +23,7 @@ func (request HealthGETRequest) Request(v *voki.Voki) (response HealthGETRespons statusCode, body, err := v.CallAndParse( http.MethodGet, - "/api/health/", + "/api/v7/health/", nil, true, ) @@ -64,7 +64,7 @@ func (request ProgrammesPOSTRequest) Request(v *voki.Voki) (response ProgrammesP statusCode, body, err := v.CallAndParse( http.MethodPost, - "/api/programmes/", + "/api/v7/programme/", &buf, true, ) @@ -105,7 +105,7 @@ func (request MembresPOSTRequest) Request(v *voki.Voki) (response MembresPOSTRes statusCode, body, err := v.CallAndParse( http.MethodPost, - "/api/membres/", + "/api/v7/membre/", &buf, true, ) @@ -120,3 +120,44 @@ func (request MembresPOSTRequest) Request(v *voki.Voki) (response MembresPOSTRes return } + +var _ voki.Requester[MembreGETResponse] = MembreGETRequest{} + +type MembreGETRequest struct { + Param struct { + MembreID string `json:"membre_id" param:"membre_id"` + } +} + +func (request MembreGETRequest) Complete() bool { + return request.Param.MembreID != "" +} + +func (request MembreGETRequest) Request(v *voki.Voki) (response MembreGETResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete MembreGETRequest") + return + } + + if id := request.Param.MembreID; !IsMembreID(id) { + err = fmt.Errorf("MembreID '%s' invalide", id) + return + } + + statusCode, body, err := v.CallAndParse( + http.MethodGet, + fmt.Sprintf("/api/v7/membre/%s/", request.Param.MembreID), + nil, + true, + ) + if err != nil { + err = fmt.Errorf("code=%d err=%s", statusCode, err) + return + } + response.SetStatusCode(statusCode) + if err = json.Unmarshal(body, &response); err != nil { + return + } + + return +} diff --git a/routes.go b/routes.go index 05c62b5..c5b3083 100644 --- a/routes.go +++ b/routes.go @@ -12,7 +12,7 @@ import ( func addRoutes(e *echo.Echo, db *PostgresClient) error { _ = db - apiPath := "/api" + apiPath := "/api/v7" apiGroup := e.Group(apiPath) p := pave.New() if err := pave.EchoRegister[HealthGETRequest]( @@ -47,7 +47,7 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { &p, apiPath, http.MethodPost, - "/programmes/", + "/programme/", "Insert programmes", "ProgrammesPOST", func(c echo.Context) error { var request, response = ProgrammesPOSTRequest{}, ProgrammesPOSTResponse{} @@ -90,7 +90,7 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { &p, apiPath, http.MethodPost, - "/membres/", + "/membre/", "Insert membres", "MembresPOST", func(c echo.Context) error { var request, response = MembresPOSTRequest{}, MembresPOSTResponse{} @@ -127,5 +127,44 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { }); err != nil { return err } + + if err := pave.EchoRegister[MembreGETRequest]( + apiGroup, + &p, + apiPath, + http.MethodGet, + "/membre/:membre_id/", + "Get membre", + "MembreGET", func(c echo.Context) error { + var request, response = MembreGETRequest{}, MembreGETResponse{} + + request.Param.MembreID = c.Param("membre_id") + + if !request.Complete() { + var response voki.ResponseBadRequest + response.Message = "Incomplete MembreGET request received" + return c.JSON(response.StatusCode(), response) + } + + membre, err := db.GetMembre(request.Param.MembreID) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("db: %s", err) + return c.JSON(response.StatusCode(), response) + } + response.Data.Membre = membre + + if err := response.SetStatusCode(http.StatusOK); err != nil { + var response voki.ResponseInternalServerError + response.Message = fmt.Sprintf("handler: %s", err) + return c.JSON(response.StatusCode(), response) + } + + response.Message = "ok" + return c.JSON(response.StatusCode(), response) + + }); err != nil { + return err + } return nil } From 00aebc2ae317e49d1cf4517c61619de19e485835 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 18 Jun 2024 19:47:28 -0400 Subject: [PATCH 25/61] feature: add basic Makefile for integration testing --- Makefile | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..31a6c23 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +## This Makefile uses the help target explained in the following blogpost: +## +## https://victoria.dev/blog/how-to-create-a-self-documenting-makefile/ + +.DEFAULT_GOAL := help + +.PHONY: help +help: ## Show this help + @egrep -h '\s##\s' $(MAKEFILE_LIST) | \ + sort | \ + awk \ + 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +.PHONY: test-integration +test-integration: ## run integration tests through API client. Config is read from `~/.bottin.yaml`. WARNING: affects data in the database, do not run on production server + docker-compose down && docker-compose up -d --build && sleep 2 && go test From f8b5c720036b5d37bd1415ed4628ef002e08cc90 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 18 Jun 2024 21:21:30 -0400 Subject: [PATCH 26/61] feature: add and test GetMembres --- client.go | 18 ++++++++++++++++++ client_test.go | 19 ++++++++++--------- db.go | 21 +++++++++++---------- request.go | 34 ++++++++++++++++++++++++++++++++++ routes.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 19 deletions(-) diff --git a/client.go b/client.go index d47a723..386dbdd 100644 --- a/client.go +++ b/client.go @@ -75,3 +75,21 @@ func (c APIClient) GetMembre(membreID string) (membre Membre, err error) { return response.Data.Membre, nil } + +func (c APIClient) GetMembres(limit int) (membres []Membre, err error) { + var request MembresGETRequest + + request.Query.Limit = limit + + response, err := request.Request(c.Voki) + if err != nil { + return + } + + if code, message := response.StatusCode(), response.Message; code >= 400 { + err = fmt.Errorf("%d: %s", code, message) + return + } + + return response.Data.Membres, nil +} diff --git a/client_test.go b/client_test.go index 7a847ad..7a7a62d 100644 --- a/client_test.go +++ b/client_test.go @@ -111,13 +111,14 @@ func TestAPI(t *testing.T) { }) */ //TODO get membres - /* - t.Run("get membres, max 50", - func(t *testing.T) { - membres, err := apiClient.GetMembres(50) - if err != nil { - t.Error(err) - } - }) - */ + t.Run("get membres, max 50", + func(t *testing.T) { + membres, err := apiClient.GetMembres(50) + if err != nil { + t.Error(err) + } + t.Log(membres) + }) + + //TODO remove test membres and programmes } diff --git a/db.go b/db.go index bb401ae..90d24f7 100644 --- a/db.go +++ b/db.go @@ -164,24 +164,25 @@ func (d *PostgresClient) UpdateMembreName(membreID, newName string) (int64, erro } */ -func (d *PostgresClient) GetMembres() (membres []Membre, err error) { +func (d *PostgresClient) GetMembres(limit int) (membres []Membre, err error) { select { case <-d.Ctx.Done(): return nil, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) default: rows, err := d.Pool.Query(d.Ctx, ` SELECT - membres.id, - membres.last_name, - membres.first_name, - membres.prefered_name, - membres.programme_id + "membres".id, + "membres".last_name, + "membres".first_name, + "membres".prefered_name, + "membres".programme_id FROM - membres -LIMIT - 10000 + "membres" ORDER BY - membres.id;`) + "membres".id +LIMIT + $1; +`, limit) if err != nil { return nil, err } diff --git a/request.go b/request.go index 277e6f4..df538b3 100644 --- a/request.go +++ b/request.go @@ -161,3 +161,37 @@ func (request MembreGETRequest) Request(v *voki.Voki) (response MembreGETRespons return } + +var _ voki.Requester[MembresGETResponse] = MembresGETRequest{} + +type MembresGETRequest struct { + Query struct { + Limit int `json:"limit" query:"limit"` + } +} + +func (request MembresGETRequest) Complete() bool { return true } + +func (request MembresGETRequest) Request(v *voki.Voki) (response MembresGETResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete MembresGETRequest") + return + } + + statusCode, body, err := v.CallAndParse( + http.MethodGet, + fmt.Sprintf("/api/v7/membre/?limit=%d", request.Query.Limit), + nil, + true, + ) + if err != nil { + err = fmt.Errorf("code=%d err=%s", statusCode, err) + return + } + response.SetStatusCode(statusCode) + if err = json.Unmarshal(body, &response); err != nil { + return + } + + return +} diff --git a/routes.go b/routes.go index c5b3083..412141a 100644 --- a/routes.go +++ b/routes.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net/http" + "strconv" "codeberg.org/vlbeaudoin/pave/v2" "codeberg.org/vlbeaudoin/voki/v3" @@ -166,5 +167,48 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { }); err != nil { return err } + + if err := pave.EchoRegister[MembresGETRequest]( + apiGroup, + &p, + apiPath, + http.MethodGet, + "/membre/", + "Get membres", + "MembresGET", func(c echo.Context) (err error) { + var request, response = MembresGETRequest{}, MembresGETResponse{} + + request.Query.Limit, err = strconv.Atoi(c.QueryParam("limit")) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parsing limit: %s", err) + return c.JSON(response.StatusCode(), response) + } + + if !request.Complete() { + var response voki.ResponseBadRequest + response.Message = "Incomplete MembresGET request received" + return c.JSON(response.StatusCode(), response) + } + + response.Data.Membres, err = db.GetMembres(request.Query.Limit) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("db: %s", err) + return c.JSON(response.StatusCode(), response) + } + + if err := response.SetStatusCode(http.StatusOK); err != nil { + var response voki.ResponseInternalServerError + response.Message = fmt.Sprintf("handler: %s", err) + return c.JSON(response.StatusCode(), response) + } + + response.Message = "ok" + return c.JSON(response.StatusCode(), response) + + }); err != nil { + return err + } return nil } From f6ffa0337969028d566f92cfa915276296f4b8c6 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 18 Jun 2024 22:51:20 -0400 Subject: [PATCH 27/61] feature: ajouter MembrePreferedNamePUTResponse --- response.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/response.go b/response.go index 55a700d..cf53a80 100644 --- a/response.go +++ b/response.go @@ -33,6 +33,10 @@ type MembreGETResponseData struct { Membre Membre `json:"membre"` } +type MembrePreferedNamePUTResponse struct { + APIResponse +} + type MembresGETResponse struct { APIResponse Data MembresGETResponseData `json:"data"` From 4d338f2b03318605e4c9a0c18256b2c82f75eafa Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 18 Jun 2024 22:51:32 -0400 Subject: [PATCH 28/61] feature: ajouter ProgrammesGETResponse et data --- response.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/response.go b/response.go index cf53a80..9f0d076 100644 --- a/response.go +++ b/response.go @@ -63,3 +63,12 @@ type ProgrammesPOSTResponse struct { type ProgrammesPOSTResponseData struct { ProgrammesInserted int64 `json:"programmes_inserted"` } + +type ProgrammesGETResponse struct { + APIResponse + Data ProgrammesGETResponseData `json:"data"` +} + +type ProgrammesGETResponseData struct { + Programmes []Programme `json:"programmes"` +} From 26b3134861d36e8a71ed515acdae9612fd5720f7 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 18 Jun 2024 23:55:55 -0400 Subject: [PATCH 29/61] feature(request): ajouter MembrePreferedNamePUT et ProgrammesGET --- request.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/request.go b/request.go index df538b3..ab4642d 100644 --- a/request.go +++ b/request.go @@ -195,3 +195,81 @@ func (request MembresGETRequest) Request(v *voki.Voki) (response MembresGETRespo return } + +var _ voki.Requester[MembrePreferedNamePUTResponse] = MembrePreferedNamePUTRequest{} + +type MembrePreferedNamePUTRequest struct { + Data struct { + PreferedName string `json:"prefered_name"` + } `json:"data"` + Param struct { + MembreID string `json:"membre_id" param:"membre_id"` + } `json:"param"` +} + +func (request MembrePreferedNamePUTRequest) Complete() bool { + return IsMembreID(request.Param.MembreID) && len(request.Data.PreferedName) != 0 +} + +func (request MembrePreferedNamePUTRequest) Request(v *voki.Voki) (response MembrePreferedNamePUTResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete MembrePreferedNamePUTRequest") + return + } + + var buf bytes.Buffer + if err = json.NewEncoder(&buf).Encode(request.Data); err != nil { + return + } + + statusCode, body, err := v.CallAndParse( + http.MethodPut, + "/api/v7/membre/%s/", + &buf, + true, + ) + if err != nil { + err = fmt.Errorf("code=%d err=%s", statusCode, err) + return + } + response.SetStatusCode(statusCode) + if err = json.Unmarshal(body, &response); err != nil { + return + } + + return +} + +var _ voki.Requester[ProgrammesGETResponse] = ProgrammesGETRequest{} + +type ProgrammesGETRequest struct { + Query struct { + Limit int `json:"limit" query:"limit"` + } +} + +func (request ProgrammesGETRequest) Complete() bool { return true } + +func (request ProgrammesGETRequest) Request(v *voki.Voki) (response ProgrammesGETResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete ProgrammesGETRequest") + return + } + + statusCode, body, err := v.CallAndParse( + http.MethodGet, + fmt.Sprintf("/api/v7/programme/?limit=%d", request.Query.Limit), + nil, + true, + ) + if err != nil { + err = fmt.Errorf("code=%d err=%s", statusCode, err) + return + } + response.SetStatusCode(statusCode) + if err = json.Unmarshal(body, &response); err != nil { + return + } + + return +} From 78aafe0ce9953154fd94f4ff38124da8d737081e Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 19 Jun 2024 00:04:19 -0400 Subject: [PATCH 30/61] feature(api): add and test ProgrammesGET --- client.go | 18 ++++++++++++++++++ client_test.go | 14 +++++++++----- db.go | 41 +++++++++++++++++++++++++++++++++++++++++ routes.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 5 deletions(-) diff --git a/client.go b/client.go index 386dbdd..28ac08c 100644 --- a/client.go +++ b/client.go @@ -93,3 +93,21 @@ func (c APIClient) GetMembres(limit int) (membres []Membre, err error) { return response.Data.Membres, nil } + +func (c APIClient) GetProgrammes(limit int) (programmes []Programme, err error) { + var request ProgrammesGETRequest + + request.Query.Limit = limit + + response, err := request.Request(c.Voki) + if err != nil { + return + } + + if code, message := response.StatusCode(), response.Message; code >= 400 { + err = fmt.Errorf("%d: %s", code, message) + return + } + + return response.Data.Programmes, nil +} diff --git a/client_test.go b/client_test.go index 7a7a62d..7d3ad78 100644 --- a/client_test.go +++ b/client_test.go @@ -39,8 +39,6 @@ func TestAPI(t *testing.T) { } }) - //TODO create or replace schema - t.Run("insert programmes", func(t *testing.T) { programmes := []Programme{ @@ -70,6 +68,15 @@ func TestAPI(t *testing.T) { }, } + t.Run("get programmes, max 50", + func(t *testing.T) { + programmes, err := apiClient.GetProgrammes(50) + if err != nil { + t.Error(err) + } + t.Log(programmes) + }) + t.Run("insert membres", func(t *testing.T) { _, err := apiClient.InsertMembres(testMembres...) @@ -110,7 +117,6 @@ func TestAPI(t *testing.T) { } }) */ - //TODO get membres t.Run("get membres, max 50", func(t *testing.T) { membres, err := apiClient.GetMembres(50) @@ -119,6 +125,4 @@ func TestAPI(t *testing.T) { } t.Log(membres) }) - - //TODO remove test membres and programmes } diff --git a/db.go b/db.go index 90d24f7..3ae7ee0 100644 --- a/db.go +++ b/db.go @@ -210,3 +210,44 @@ LIMIT return membres, nil } } + +func (d *PostgresClient) GetProgrammes(limit int) (programmes []Programme, err error) { + select { + case <-d.Ctx.Done(): + return nil, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) + default: + rows, err := d.Pool.Query(d.Ctx, ` +SELECT + "programmes".id, + "programmes".name +FROM + "programmes" +ORDER BY + "programmes".id +LIMIT + $1; +`, limit) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var programme Programme + + if err = rows.Scan( + &programme.ID, + &programme.Name, + ); err != nil { + return nil, err + } + + programmes = append(programmes, programme) + } + if rows.Err() != nil { + return programmes, rows.Err() + } + + return programmes, nil + } +} diff --git a/routes.go b/routes.go index 412141a..1ec580e 100644 --- a/routes.go +++ b/routes.go @@ -210,5 +210,48 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { }); err != nil { return err } + + if err := pave.EchoRegister[ProgrammesGETRequest]( + apiGroup, + &p, + apiPath, + http.MethodGet, + "/programme/", + "Get programmes", + "ProgrammesGET", func(c echo.Context) (err error) { + var request, response = ProgrammesGETRequest{}, ProgrammesGETResponse{} + + request.Query.Limit, err = strconv.Atoi(c.QueryParam("limit")) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parsing limit: %s", err) + return c.JSON(response.StatusCode(), response) + } + + if !request.Complete() { + var response voki.ResponseBadRequest + response.Message = "Incomplete ProgrammesGET request received" + return c.JSON(response.StatusCode(), response) + } + + response.Data.Programmes, err = db.GetProgrammes(request.Query.Limit) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("db: %s", err) + return c.JSON(response.StatusCode(), response) + } + + if err := response.SetStatusCode(http.StatusOK); err != nil { + var response voki.ResponseInternalServerError + response.Message = fmt.Sprintf("handler: %s", err) + return c.JSON(response.StatusCode(), response) + } + + response.Message = "ok" + return c.JSON(response.StatusCode(), response) + + }); err != nil { + return err + } return nil } From e6103c6e6e461605edc2472658daabf95d477e4d Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 19 Jun 2024 00:28:26 -0400 Subject: [PATCH 31/61] feature(api): add and test UpdateMembrePreferedName --- client.go | 22 ++++++++++++++++++++++ client_test.go | 15 ++++++--------- db.go | 21 +++++++++++++++++++++ request.go | 2 +- routes.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 10 deletions(-) diff --git a/client.go b/client.go index 28ac08c..91cdbe7 100644 --- a/client.go +++ b/client.go @@ -111,3 +111,25 @@ func (c APIClient) GetProgrammes(limit int) (programmes []Programme, err error) return response.Data.Programmes, nil } + +func (c APIClient) UpdateMembrePreferedName(membreID string, name string) (err error) { + var request MembrePreferedNamePUTRequest + + if !IsMembreID(membreID) { + return fmt.Errorf("Numéro étudiant '%s' invalide", membreID) + } + request.Param.MembreID = membreID + request.Data.PreferedName = name + + response, err := request.Request(c.Voki) + if err != nil { + return + } + + if code, message := response.StatusCode(), response.Message; code >= 400 { + err = fmt.Errorf("%d: %s", code, message) + return + } + + return nil +} diff --git a/client_test.go b/client_test.go index 7d3ad78..1a02a15 100644 --- a/client_test.go +++ b/client_test.go @@ -108,15 +108,12 @@ func TestAPI(t *testing.T) { } }) - //TODO update membre prefered name - /* - t.Run("", - func(t *testing.T) { - if err := apiClient.UpdateMembrePreferedName(testMembres[0].ID, "User, Galaxy"); err != nil { - t.Error(err) - } - }) - */ + t.Run("update membre prefered name", + func(t *testing.T) { + if err := apiClient.UpdateMembrePreferedName(testMembres[0].ID, "User, Galaxy"); err != nil { + t.Error(err) + } + }) t.Run("get membres, max 50", func(t *testing.T) { membres, err := apiClient.GetMembres(50) diff --git a/db.go b/db.go index 3ae7ee0..2608940 100644 --- a/db.go +++ b/db.go @@ -251,3 +251,24 @@ LIMIT return programmes, nil } } + +func (d *PostgresClient) UpdateMembrePreferedName(membreID string, name string) (err error) { + select { + case <-d.Ctx.Done(): + return fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) + default: + if !IsMembreID(membreID) { + return fmt.Errorf("Numéro étudiant '%s' invalide", membreID) + } + + _, err = d.Pool.Exec(d.Ctx, ` +UPDATE + "membres" +SET + prefered_name = $1 +WHERE + "membres".id = $2; +`, name, membreID) + } + return +} diff --git a/request.go b/request.go index ab4642d..97f51de 100644 --- a/request.go +++ b/request.go @@ -224,7 +224,7 @@ func (request MembrePreferedNamePUTRequest) Request(v *voki.Voki) (response Memb statusCode, body, err := v.CallAndParse( http.MethodPut, - "/api/v7/membre/%s/", + fmt.Sprintf("/api/v7/membre/%s/prefered_name/", request.Param.MembreID), &buf, true, ) diff --git a/routes.go b/routes.go index 1ec580e..355dd8f 100644 --- a/routes.go +++ b/routes.go @@ -253,5 +253,49 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { }); err != nil { return err } + + if err := pave.EchoRegister[MembrePreferedNamePUTRequest]( + apiGroup, + &p, + apiPath, + http.MethodPut, + "/membre/:membre_id/prefered_name/", + "Update membre prefered name, which is prioritized in the membres_for_display view", + "MembrePreferedNamePUT", func(c echo.Context) error { + var request, response = MembrePreferedNamePUTRequest{}, MembrePreferedNamePUTResponse{} + + request.Param.MembreID = c.Param("membre_id") + + if err := c.Bind(&request.Data); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse request body: %s", err) + return c.JSON(response.StatusCode(), response) + } + + if !request.Complete() { + var response voki.ResponseBadRequest + response.Message = "Incomplete MembrePreferedNamePUT request received" + return c.JSON(response.StatusCode(), response) + } + + if err := db.UpdateMembrePreferedName(request.Param.MembreID, request.Data.PreferedName); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("db: %s", err) + return c.JSON(response.StatusCode(), response) + } + + if err := response.SetStatusCode(http.StatusOK); err != nil { + var response voki.ResponseInternalServerError + response.Message = fmt.Sprintf("handler: %s", err) + return c.JSON(response.StatusCode(), response) + } + + response.Message = fmt.Sprintf("Updated membre %s name to %s", request.Param.MembreID, request.Data.PreferedName) + return c.JSON(response.StatusCode(), response) + + }); err != nil { + return err + } + return nil } From e4ff1013d04288a8fbb3710506cebfb3b9b7cd8a Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 20 Jun 2024 18:51:38 -0400 Subject: [PATCH 32/61] feature: ajouter et tester GetMembre[s]ForDisplay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Priorisent le prefered_name ("nom d'usage") et devraient être utilisés aux endroits où l'affichage est important. --- client.go | 35 +++++++++++++++++++++ client_test.go | 18 +++++++++++ db.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ entity.go | 8 +++++ request.go | 75 +++++++++++++++++++++++++++++++++++++++++++++ response.go | 17 +++++++++++ routes.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 315 insertions(+) diff --git a/client.go b/client.go index 91cdbe7..e4958a6 100644 --- a/client.go +++ b/client.go @@ -133,3 +133,38 @@ func (c APIClient) UpdateMembrePreferedName(membreID string, name string) (err e return nil } + +func (c APIClient) GetMembreForDisplay(membreID string) (membre MembreForDisplay, err error) { + var request MembreDisplayGETRequest + request.Param.MembreID = membreID + + response, err := request.Request(c.Voki) + if err != nil { + return + } + + if code, message := response.StatusCode(), response.Message; code >= 400 { + err = fmt.Errorf("%d: %s", code, message) + return + } + + return response.Data.Membre, nil +} + +func (c APIClient) GetMembresForDisplay(limit int) (membres []MembreForDisplay, err error) { + var request MembresDisplayGETRequest + + request.Query.Limit = limit + + response, err := request.Request(c.Voki) + if err != nil { + return + } + + if code, message := response.StatusCode(), response.Message; code >= 400 { + err = fmt.Errorf("%d: %s", code, message) + return + } + + return response.Data.Membres, nil +} diff --git a/client_test.go b/client_test.go index 1a02a15..a2aea93 100644 --- a/client_test.go +++ b/client_test.go @@ -122,4 +122,22 @@ func TestAPI(t *testing.T) { } t.Log(membres) }) + + t.Run("get membre for display", + func(t *testing.T) { + membre, err := apiClient.GetMembreForDisplay(testMembres[0].ID) + if err != nil { + t.Error(err) + } + t.Log(membre) + }) + + t.Run("get membres for display, max 5", + func(t *testing.T) { + membres, err := apiClient.GetMembresForDisplay(5) + if err != nil { + t.Error(err) + } + t.Log(membres) + }) } diff --git a/db.go b/db.go index 2608940..915ddab 100644 --- a/db.go +++ b/db.go @@ -272,3 +272,83 @@ WHERE } return } + +func (d *PostgresClient) GetMembreForDisplay(membreID string) (membre MembreForDisplay, err error) { + select { + case <-d.Ctx.Done(): + err = fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) + return + default: + if err = d.Pool.QueryRow(d.Ctx, ` +SELECT + "membres_for_display".id, + "membres_for_display".name, + "membres_for_display".programme_id, + "membres_for_display".programme_name +FROM + "membres_for_display" +WHERE + "membres_for_display".id = $1 +LIMIT + 1; +`, membreID).Scan( + &membre.ID, + &membre.Name, + &membre.ProgrammeID, + &membre.ProgrammeName, + ); err != nil { + return + } + + if membre.ID == "" { + return membre, fmt.Errorf("Aucun membre trouvé avec numéro '%s'", membre.ID) + } + + return membre, nil + } +} + +func (d *PostgresClient) GetMembresForDisplay(limit int) (membres []MembreForDisplay, err error) { + select { + case <-d.Ctx.Done(): + return nil, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err()) + default: + rows, err := d.Pool.Query(d.Ctx, ` +SELECT + "membres_for_display".id, + "membres_for_display".name, + "membres_for_display".programme_id, + "membres_for_display".programme_name +FROM + "membres_for_display" +ORDER BY + "membres_for_display".id +LIMIT + $1; +`, limit) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var membre MembreForDisplay + + if err = rows.Scan( + &membre.ID, + &membre.Name, + &membre.ProgrammeID, + &membre.ProgrammeName, + ); err != nil { + return nil, err + } + + membres = append(membres, membre) + } + if rows.Err() != nil { + return membres, rows.Err() + } + + return membres, nil + } +} diff --git a/entity.go b/entity.go index b919c29..672f54c 100644 --- a/entity.go +++ b/entity.go @@ -15,6 +15,14 @@ type Membre struct { ProgrammeID string `db:"programme_id" json:"programme_id" csv:"programme_id"` } +// MembreForDisplay maps to the `membres_for_display` view declared in `sql/views.sql` +type MembreForDisplay struct { + ID string `db:"id" json:"membre_id" csv:"membre_id"` + Name string `db:"name" json:"name" csv:"name"` + ProgrammeID string `db:"programme_id" json:"programme_id" csv:"programme_id"` + ProgrammeName string `db:"programme_name" json:"programme_name" csv:"programme_name"` +} + func IsMembreID(membre_id string) bool { if len(membre_id) != 7 { return false diff --git a/request.go b/request.go index 97f51de..8f47f10 100644 --- a/request.go +++ b/request.go @@ -273,3 +273,78 @@ func (request ProgrammesGETRequest) Request(v *voki.Voki) (response ProgrammesGE return } + +var _ voki.Requester[MembresDisplayGETResponse] = MembresDisplayGETRequest{} + +type MembresDisplayGETRequest struct { + Query struct { + Limit int `json:"limit" query:"limit"` + } +} + +func (request MembresDisplayGETRequest) Complete() bool { return true } + +func (request MembresDisplayGETRequest) Request(v *voki.Voki) (response MembresDisplayGETResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete MembresDisplayGETRequest") + return + } + + statusCode, body, err := v.CallAndParse( + http.MethodGet, + fmt.Sprintf("/api/v7/membre/display/?limit=%d", request.Query.Limit), + nil, + true, + ) + if err != nil { + err = fmt.Errorf("code=%d err=%s", statusCode, err) + return + } + response.SetStatusCode(statusCode) + if err = json.Unmarshal(body, &response); err != nil { + return + } + + return +} + +var _ voki.Requester[MembreDisplayGETResponse] = MembreDisplayGETRequest{} + +type MembreDisplayGETRequest struct { + Param struct { + MembreID string `json:"membre_id" param:"membre_id"` + } +} + +func (request MembreDisplayGETRequest) Complete() bool { + return request.Param.MembreID != "" +} + +func (request MembreDisplayGETRequest) Request(v *voki.Voki) (response MembreDisplayGETResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete MembreDisplayGETRequest") + return + } + + if id := request.Param.MembreID; !IsMembreID(id) { + err = fmt.Errorf("MembreID '%s' invalide", id) + return + } + + statusCode, body, err := v.CallAndParse( + http.MethodGet, + fmt.Sprintf("/api/v7/membre/%s/display/", request.Param.MembreID), + nil, + true, + ) + if err != nil { + err = fmt.Errorf("code=%d err=%s", statusCode, err) + return + } + response.SetStatusCode(statusCode) + if err = json.Unmarshal(body, &response); err != nil { + return + } + + return +} diff --git a/response.go b/response.go index 9f0d076..19818df 100644 --- a/response.go +++ b/response.go @@ -46,6 +46,23 @@ type MembresGETResponseData struct { Membres []Membre `json:"membres"` } +type MembreDisplayGETResponse struct { + APIResponse + Data MembreDisplayGETResponseData `json:"data"` +} +type MembreDisplayGETResponseData struct { + Membre MembreForDisplay `json:"membre_for_display"` +} + +type MembresDisplayGETResponse struct { + APIResponse + Data MembresDisplayGETResponseData `json:"data"` +} + +type MembresDisplayGETResponseData struct { + Membres []MembreForDisplay `json:"membres_for_display"` +} + type MembresPOSTResponse struct { APIResponse Data MembresPOSTResponseData `json:"data"` diff --git a/routes.go b/routes.go index 355dd8f..a5aaed6 100644 --- a/routes.go +++ b/routes.go @@ -297,5 +297,87 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { return err } + if err := pave.EchoRegister[MembresDisplayGETRequest]( + apiGroup, + &p, + apiPath, + http.MethodGet, + "/membre/display/", + "Get membres", + "MembresDisplayGET", func(c echo.Context) (err error) { + var request, response = MembresDisplayGETRequest{}, MembresDisplayGETResponse{} + + request.Query.Limit, err = strconv.Atoi(c.QueryParam("limit")) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parsing limit: %s", err) + return c.JSON(response.StatusCode(), response) + } + + if !request.Complete() { + var response voki.ResponseBadRequest + response.Message = "Incomplete MembresDisplayGET request received" + return c.JSON(response.StatusCode(), response) + } + + response.Data.Membres, err = db.GetMembresForDisplay(request.Query.Limit) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("db: %s", err) + return c.JSON(response.StatusCode(), response) + } + + if err := response.SetStatusCode(http.StatusOK); err != nil { + var response voki.ResponseInternalServerError + response.Message = fmt.Sprintf("handler: %s", err) + return c.JSON(response.StatusCode(), response) + } + + response.Message = "ok" + return c.JSON(response.StatusCode(), response) + + }); err != nil { + return err + } + + if err := pave.EchoRegister[MembreDisplayGETRequest]( + apiGroup, + &p, + apiPath, + http.MethodGet, + "/membre/:membre_id/display/", + "Get membre", + "MembreDisplayGET", func(c echo.Context) error { + var request, response = MembreDisplayGETRequest{}, MembreDisplayGETResponse{} + + request.Param.MembreID = c.Param("membre_id") + + if !request.Complete() { + var response voki.ResponseBadRequest + response.Message = "Incomplete MembreDisplayGET request received" + return c.JSON(response.StatusCode(), response) + } + + membre, err := db.GetMembreForDisplay(request.Param.MembreID) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("db: %s", err) + return c.JSON(response.StatusCode(), response) + } + response.Data.Membre = membre + + if err := response.SetStatusCode(http.StatusOK); err != nil { + var response voki.ResponseInternalServerError + response.Message = fmt.Sprintf("handler: %s", err) + return c.JSON(response.StatusCode(), response) + } + + response.Message = "ok" + return c.JSON(response.StatusCode(), response) + + }); err != nil { + return err + } + return nil } From 929704c6ff25c9fc0bd4a16f9fbf36f79674dff5 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 20 Jun 2024 19:32:26 -0400 Subject: [PATCH 33/61] =?UTF-8?q?fix(config):=20ajouter=20pr=C3=A9fixe=20w?= =?UTF-8?q?eb[.-]=20aux=20options=20config=20web?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config.go b/config.go index d50e241..3096960 100644 --- a/config.go +++ b/config.go @@ -66,23 +66,23 @@ const ( DefaultWebPort int = 2312 DescriptionWebPort string = "Web client port" - ViperWebAPIHost string = "api.host" - FlagWebAPIHost string = "api-host" + ViperWebAPIHost string = "web.api.host" + FlagWebAPIHost string = "web-api-host" DefaultWebAPIHost string = "api" DescriptionWebAPIHost string = "Target API server host" - ViperWebAPIKey string = "api.key" - FlagWebAPIKey string = "api-key" + ViperWebAPIKey string = "web.api.key" + FlagWebAPIKey string = "web-api-key" DefaultWebAPIKey string = "bottin" DescriptionWebAPIKey string = "Target API server key" - ViperWebAPIPort string = "api.port" - FlagWebAPIPort string = "api-port" + ViperWebAPIPort string = "web.api.port" + FlagWebAPIPort string = "web-api-port" DefaultWebAPIPort int = 1312 DescriptionWebAPIPort string = "Target API server port" - ViperWebAPIProtocol string = "api.protocol" - FlagWebAPIProtocol string = "api-protocol" + ViperWebAPIProtocol string = "web.api.protocol" + FlagWebAPIProtocol string = "web-api-protocol" DefaultWebAPIProtocol string = "http" DescriptionWebAPIProtocol string = "Target API server protocol (http/https)" ) From 8cb2014f3bafddb56c0dea3051988a3043a98a8a Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 20 Jun 2024 19:34:27 -0400 Subject: [PATCH 34/61] fix(template): expect voki.MessageResponse in input object --- templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/index.html b/templates/index.html index 700d9bb..85c857d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -100,7 +100,7 @@ button { -

{{ .Result }}

+

{{ .Message }}

From 7484bafc84252ae468dc094a3ed084640c0ad2eb Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 20 Jun 2024 19:35:07 -0400 Subject: [PATCH 35/61] =?UTF-8?q?fix(web):=20neutraliser=20texte=20avec=20?= =?UTF-8?q?middle=20dot=20(=C2=B7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/index.html b/templates/index.html index 85c857d..2822147 100644 --- a/templates/index.html +++ b/templates/index.html @@ -83,7 +83,7 @@ button {

- Scannez la carte étudiante d'unE membre
+ Scannez la carte étudiante d'un·e membre
-ou-
Entrez manuellement le code à 7 chiffres

From 244276905b94253aad868032d388847ef1ffeab4 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 20 Jun 2024 19:36:38 -0400 Subject: [PATCH 36/61] =?UTF-8?q?feature(cmd):=20impl=C3=A9menter=20webCmd?= =?UTF-8?q?=20de=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit manque encore le processus de scan mais sinon c'est presque fini --- cmd.go | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/cmd.go b/cmd.go index 6067795..dd41f0b 100644 --- a/cmd.go +++ b/cmd.go @@ -6,8 +6,10 @@ import ( "fmt" "html/template" "log" + "net/http" "os" + "codeberg.org/vlbeaudoin/voki/v3" "github.com/jackc/pgx/v5/pgxpool" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -122,56 +124,56 @@ var webCmd = &cobra.Command{ Short: "Démarrer le client web", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { + // Parse config var cfg Config if err := viper.Unmarshal(&cfg); err != nil { log.Fatal("init config:", err) } - // Ping API server - /* - client := http.DefaultClient - defer client.CloseIdleConnections() - - apiClient := data.NewApiClient(client, webApiKey, webApiHost, webApiProtocol, webApiPort) - - pingResult, err := apiClient.GetHealth() - if err != nil { - log.Fatal(err) - } - - log.Println(pingResult) - */ - e := echo.New() // Middlewares + // Trailing slash e.Pre(middleware.AddTrailingSlash()) + // Auth e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Web.User)) == 1 passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Web.Password)) == 1 return usersMatch && passwordsMatch, nil })) - // Template - - t := &Template{ + // Templating + e.Renderer = &Template{ templates: template.Must(template.ParseFS(templatesFS, "templates/*.html")), } - e.Renderer = t + // API Client + apiClient := APIClient{voki.New( + http.DefaultClient, + cfg.Web.API.Host, + cfg.Web.API.Key, + cfg.Web.API.Port, + cfg.Web.API.Protocol, + )} + defer apiClient.Voki.CloseIdleConnections() // Routes - /* - handler := webhandlers.Handler{APIClient: apiClient} + e.GET("/", func(c echo.Context) error { + pingResult, err := apiClient.GetHealth() + if err != nil { + log.Fatal(err) + } - e.GET("/", handler.GetIndex) - e.GET("/membre/", handler.GetMembre) - */ + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: pingResult}, + ) + }) // Execution - e.Logger.Fatal(e.Start( fmt.Sprintf(":%d", cfg.Web.Port))) }, From 0321b1b2a0c9bf2b91e617c95aee61b61dec0781 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 20 Jun 2024 19:54:41 -0400 Subject: [PATCH 37/61] =?UTF-8?q?fix(web):=20correctement=20render=20erreu?= =?UTF-8?q?r=20d'acc=C3=A8s=20au=20serveur=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd.go b/cmd.go index dd41f0b..88f36a5 100644 --- a/cmd.go +++ b/cmd.go @@ -163,7 +163,11 @@ var webCmd = &cobra.Command{ e.GET("/", func(c echo.Context) error { pingResult, err := apiClient.GetHealth() if err != nil { - log.Fatal(err) + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: fmt.Sprintf("impossible d'accéder au serveur API: %s", err)}, + ) } return c.Render( From 6cc90b1a29cb56e88519689de66c38586702f4dc Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 20 Jun 2024 19:55:12 -0400 Subject: [PATCH 38/61] feature(web): ajouter route /membre/ permet la recherche de membre --- cmd.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/cmd.go b/cmd.go index 88f36a5..3bbd59c 100644 --- a/cmd.go +++ b/cmd.go @@ -177,6 +177,48 @@ var webCmd = &cobra.Command{ ) }) + e.GET("/membre/", func(c echo.Context) error { + membreID := c.QueryParam("membre_id") + switch { + case membreID == "": + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: "Veuillez entrer un numéro étudiant à rechercher"}, + ) + case !IsMembreID(membreID): + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: fmt.Sprintf("Numéro étudiant '%s' invalide", membreID)}, + ) + } + + membre, err := apiClient.GetMembreForDisplay(membreID) + if err != nil { + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: fmt.Sprintf("erreur: %s", err)}, + ) + } + + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: fmt.Sprintf(` +Numéro étudiant: %s +Nom d'usage: %s +Programme: [%s] %s +`, + membre.ID, + membre.Name, + membre.ProgrammeID, + membre.ProgrammeName, + )}, + ) + }) + // Execution e.Logger.Fatal(e.Start( fmt.Sprintf(":%d", cfg.Web.Port))) From 8af11615dda87c67d01090c8eaaea87aef285f50 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 20 Jun 2024 20:16:33 -0400 Subject: [PATCH 39/61] =?UTF-8?q?adjust:=20ajouter=20emojis=20=C3=A0=20cer?= =?UTF-8?q?taines=20web=20responses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd.go b/cmd.go index 3bbd59c..591bdc8 100644 --- a/cmd.go +++ b/cmd.go @@ -184,13 +184,13 @@ var webCmd = &cobra.Command{ return c.Render( http.StatusOK, "index-html", - voki.MessageResponse{Message: "Veuillez entrer un numéro étudiant à rechercher"}, + voki.MessageResponse{Message: "❗Veuillez entrer un numéro étudiant à rechercher"}, ) case !IsMembreID(membreID): return c.Render( http.StatusOK, "index-html", - voki.MessageResponse{Message: fmt.Sprintf("Numéro étudiant '%s' invalide", membreID)}, + voki.MessageResponse{Message: fmt.Sprintf("❗Numéro étudiant '%s' invalide", membreID)}, ) } @@ -199,7 +199,7 @@ var webCmd = &cobra.Command{ return c.Render( http.StatusOK, "index-html", - voki.MessageResponse{Message: fmt.Sprintf("erreur: %s", err)}, + voki.MessageResponse{Message: fmt.Sprintf("❗erreur: %s", err)}, ) } From 64ddfa96d69820ce8bf2829f63486f14538cbf46 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 20 Jun 2024 20:20:30 -0400 Subject: [PATCH 40/61] =?UTF-8?q?fix:=20franciser=20erreur=20de=20membre?= =?UTF-8?q?=20non=20trouv=C3=A9=C2=B7e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/db.go b/db.go index 915ddab..f0b2f4e 100644 --- a/db.go +++ b/db.go @@ -5,6 +5,7 @@ import ( _ "embed" "fmt" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) @@ -297,6 +298,9 @@ LIMIT &membre.ProgrammeID, &membre.ProgrammeName, ); err != nil { + if err == pgx.ErrNoRows { + err = fmt.Errorf("Numéro étudiant valide mais aucun·e membre trouvé·e") + } return } From d0de8115473509ff8122a51d7153c8f97bbb3563 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Fri, 21 Jun 2024 18:46:45 -0400 Subject: [PATCH 41/61] chores: update dependencies --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index b6ce20d..a573bf5 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( codeberg.org/vlbeaudoin/voki/v3 v3.0.0 github.com/jackc/pgx/v5 v5.6.0 github.com/labstack/echo/v4 v4.12.0 - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 ) @@ -36,7 +36,7 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.24.0 // indirect - golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect diff --git a/go.sum b/go.sum index 73ace91..7d7ac8f 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,7 @@ codeberg.org/vlbeaudoin/pave/v2 v2.0.0 h1:hfB5KnqMMu17g5QBWgLvWOsqidrYaohRfu2Lfl codeberg.org/vlbeaudoin/pave/v2 v2.0.0/go.mod h1:TsTfP6IA+3Ph33vLZigeJWS5vgBPgkW1tfs3zFPfycU= codeberg.org/vlbeaudoin/voki/v3 v3.0.0 h1:XdF/UTe9YUNj3hYrAyEvdmIMDYLL8SkqTwPkqw1yJ2c= codeberg.org/vlbeaudoin/voki/v3 v3.0.0/go.mod h1:+6LMXosAu2ijNKV04sMwkeujpH+cghZU1fydqj2y95g= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -62,8 +62,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= @@ -89,8 +89,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= -golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= From 14eb6c5d02c80ec50888824558f9ae41194365bb Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 3 Jul 2024 17:33:56 -0400 Subject: [PATCH 42/61] ajouter examples/example.csv --- examples/example.csv | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/example.csv diff --git a/examples/example.csv b/examples/example.csv new file mode 100644 index 0000000..fce75ff --- /dev/null +++ b/examples/example.csv @@ -0,0 +1,3 @@ +programme_id;nom_programme; +000.00;test programme; +111.11;autre test programme; From 1f2ba0576a16fd84c0191643092025ce17131102 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 3 Jul 2024 17:34:18 -0400 Subject: [PATCH 43/61] feature: permettre insert par csv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajouter parameter cfg à addRoutes() Fix empty et default limit sur get requests (set default limit à 1000 hardcoded, todo move to config) --- cmd.go | 2 +- go.mod | 1 + go.sum | 2 + routes.go | 120 +++++++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 104 insertions(+), 21 deletions(-) diff --git a/cmd.go b/cmd.go index 591bdc8..8c5571d 100644 --- a/cmd.go +++ b/cmd.go @@ -92,7 +92,7 @@ var apiCmd = &cobra.Command{ } // Routes - if err := addRoutes(e, db); err != nil { + if err := addRoutes(e, db, cfg); err != nil { log.Fatal("add routes:", err) } /* diff --git a/go.mod b/go.mod index a573bf5..2c52fee 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.0 require ( codeberg.org/vlbeaudoin/pave/v2 v2.0.0 codeberg.org/vlbeaudoin/voki/v3 v3.0.0 + github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 github.com/jackc/pgx/v5 v5.6.0 github.com/labstack/echo/v4 v4.12.0 github.com/spf13/cobra v1.8.1 diff --git a/go.sum b/go.sum index 7d7ac8f..e1ad5b4 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= diff --git a/routes.go b/routes.go index a5aaed6..f7eb61c 100644 --- a/routes.go +++ b/routes.go @@ -1,16 +1,19 @@ package main import ( + "encoding/csv" "fmt" + "io" "net/http" "strconv" "codeberg.org/vlbeaudoin/pave/v2" "codeberg.org/vlbeaudoin/voki/v3" + "github.com/gocarina/gocsv" "github.com/labstack/echo/v4" ) -func addRoutes(e *echo.Echo, db *PostgresClient) error { +func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error { _ = db apiPath := "/api/v7" @@ -53,9 +56,37 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { "ProgrammesPOST", func(c echo.Context) error { var request, response = ProgrammesPOSTRequest{}, ProgrammesPOSTResponse{} - if err := c.Bind(&request.Data); err != nil { + switch contentType := c.Request().Header.Get("Content-Type"); contentType { + case "application/json": + if err := c.Bind(&request.Data); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse request body: %s", err) + return c.JSON(response.StatusCode(), response) + } + case "text/csv": + body := c.Request().Body + if body == nil { + var response voki.ResponseBadRequest + response.Message = "empty request body cannot be parsed" + return c.JSON(response.StatusCode(), response) + } + defer body.Close() + + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + r := csv.NewReader(in) + r.Comma = ';' + return r // Allows use ; as delimiter + }) + + // Parse CSV data using gocsv + if err := gocsv.Unmarshal(body, &request.Data.Programmes); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse programmes from csv: %s", err) + return c.JSON(response.StatusCode(), response) + } + default: var response voki.ResponseBadRequest - response.Message = fmt.Sprintf("parse request body: %s", err) + response.Message = fmt.Sprintf("cannot parse body with content-type: %s", contentType) return c.JSON(response.StatusCode(), response) } @@ -96,9 +127,37 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { "MembresPOST", func(c echo.Context) error { var request, response = MembresPOSTRequest{}, MembresPOSTResponse{} - if err := c.Bind(&request.Data); err != nil { + switch contentType := c.Request().Header.Get("Content-Type"); contentType { + case "application/json": + if err := c.Bind(&request.Data); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse request body: %s", err) + return c.JSON(response.StatusCode(), response) + } + case "text/csv": + body := c.Request().Body + if body == nil { + var response voki.ResponseBadRequest + response.Message = "empty request body cannot be parsed" + return c.JSON(response.StatusCode(), response) + } + defer body.Close() + + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + r := csv.NewReader(in) + r.Comma = ';' + return r // Allows use ; as delimiter + }) + + // Parse CSV data using gocsv + if err := gocsv.Unmarshal(body, &request.Data.Membres); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse membres from csv: %s", err) + return c.JSON(response.StatusCode(), response) + } + default: var response voki.ResponseBadRequest - response.Message = fmt.Sprintf("parse request body: %s", err) + response.Message = fmt.Sprintf("cannot parse body with content-type: %s", contentType) return c.JSON(response.StatusCode(), response) } @@ -178,11 +237,18 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { "MembresGET", func(c echo.Context) (err error) { var request, response = MembresGETRequest{}, MembresGETResponse{} - request.Query.Limit, err = strconv.Atoi(c.QueryParam("limit")) - if err != nil { - var response voki.ResponseBadRequest - response.Message = fmt.Sprintf("parsing limit: %s", err) - return c.JSON(response.StatusCode(), response) + queryLimit := c.QueryParam("limit") + if queryLimit != "" { + request.Query.Limit, err = strconv.Atoi(queryLimit) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parsing limit: %s", err) + return c.JSON(response.StatusCode(), response) + } + + } else { + //TODO cfg.API.DefaultLimit + request.Query.Limit = 1000 } if !request.Complete() { @@ -221,11 +287,18 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { "ProgrammesGET", func(c echo.Context) (err error) { var request, response = ProgrammesGETRequest{}, ProgrammesGETResponse{} - request.Query.Limit, err = strconv.Atoi(c.QueryParam("limit")) - if err != nil { - var response voki.ResponseBadRequest - response.Message = fmt.Sprintf("parsing limit: %s", err) - return c.JSON(response.StatusCode(), response) + queryLimit := c.QueryParam("limit") + if queryLimit != "" { + request.Query.Limit, err = strconv.Atoi(queryLimit) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parsing limit: %s", err) + return c.JSON(response.StatusCode(), response) + } + + } else { + //TODO cfg.API.DefaultLimit + request.Query.Limit = 1000 } if !request.Complete() { @@ -307,11 +380,18 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { "MembresDisplayGET", func(c echo.Context) (err error) { var request, response = MembresDisplayGETRequest{}, MembresDisplayGETResponse{} - request.Query.Limit, err = strconv.Atoi(c.QueryParam("limit")) - if err != nil { - var response voki.ResponseBadRequest - response.Message = fmt.Sprintf("parsing limit: %s", err) - return c.JSON(response.StatusCode(), response) + queryLimit := c.QueryParam("limit") + if queryLimit != "" { + request.Query.Limit, err = strconv.Atoi(queryLimit) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parsing limit: %s", err) + return c.JSON(response.StatusCode(), response) + } + + } else { + //TODO cfg.API.DefaultLimit + request.Query.Limit = 1000 } if !request.Complete() { From d80c7675f9ed40d1aaadd844fd69502ca6249fb9 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 3 Jul 2024 17:37:29 -0400 Subject: [PATCH 44/61] fix(routes): unused param cfg --- routes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routes.go b/routes.go index f7eb61c..ca7dcb2 100644 --- a/routes.go +++ b/routes.go @@ -15,6 +15,7 @@ import ( func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error { _ = db + _ = cfg apiPath := "/api/v7" apiGroup := e.Group(apiPath) From 150782c42f39f2063d999772ce968cc0e6dc491b Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 3 Jul 2024 20:51:43 -0400 Subject: [PATCH 45/61] feature(config): ajouter options TLS --- config.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/config.go b/config.go index 3096960..927227c 100644 --- a/config.go +++ b/config.go @@ -11,6 +11,21 @@ import ( ) const ( + ViperAPITLSEnabled string = "api.tls.enabled" + FlagAPITLSEnabled string = "api-tls-enabled" + DefaultAPITLSEnabled bool = false + DescriptionAPITLSEnabled string = "Whether to use TLS or not. Requires certificate and private key files." + + ViperAPITLSCertificateFile string = "api.tls.certificate_file" + FlagAPITLSCertificateFile string = "api-tls-certificate-file" + DefaultAPITLSCertificateFile string = "" + DescriptionAPITLSCertificateFile string = "Path to TLS certificate file" + + ViperAPITLSPrivateKeyFile string = "api.tls.private_key_file" + FlagAPITLSPrivateKeyFile string = "api-tls-private-key-file" + DefaultAPITLSPrivateKeyFile string = "" + DescriptionAPITLSPrivateKeyFile string = "Path to TLS private key file" + ViperAPIPort string = "api.port" FlagAPIPort string = "api-port" DefaultAPIPort int = 1312 @@ -89,6 +104,15 @@ const ( type Config struct { API struct { + TLS struct { + Enabled bool `yaml:"enabled"` + + // Path to file containing TLS certificate + CertificateFile string `yaml:"certificate_file"` + + // Path to file containing TLS private key + PrivateKeyFile string `yaml:"private_key_file"` + } Port int `yaml:"port"` Key string `yaml:"key"` } `yaml:"api"` @@ -116,6 +140,9 @@ type Config struct { // DefaultConfig returns a Config filled with the default values from the // `Default*` constants defined in this file. func DefaultConfig() (cfg Config) { + cfg.API.TLS.Enabled = DefaultAPITLSEnabled + cfg.API.TLS.CertificateFile = DefaultAPITLSCertificateFile + cfg.API.TLS.PrivateKeyFile = DefaultAPITLSPrivateKeyFile cfg.API.Port = DefaultAPIPort cfg.API.Key = DefaultAPIKey cfg.DB.Database = DefaultDBDatabase @@ -145,6 +172,24 @@ func init() { rootCmd.AddCommand(apiCmd) + // api.tls.enabled + apiCmd.Flags().Bool(FlagAPITLSEnabled, DefaultAPITLSEnabled, DescriptionAPITLSEnabled) + if err := viper.BindPFlag(ViperAPITLSEnabled, apiCmd.Flags().Lookup(FlagAPITLSEnabled)); err != nil { + log.Fatal(err) + } + + // api.tls.certificate_file + apiCmd.Flags().String(FlagAPITLSCertificateFile, DefaultAPITLSCertificateFile, DescriptionAPITLSCertificateFile) + if err := viper.BindPFlag(ViperAPITLSCertificateFile, apiCmd.Flags().Lookup(FlagAPITLSCertificateFile)); err != nil { + log.Fatal(err) + } + + // api.tls.private_key_file + apiCmd.Flags().String(FlagAPITLSPrivateKeyFile, DefaultAPITLSPrivateKeyFile, DescriptionAPITLSPrivateKeyFile) + if err := viper.BindPFlag(ViperAPITLSPrivateKeyFile, apiCmd.Flags().Lookup(FlagAPITLSPrivateKeyFile)); err != nil { + log.Fatal(err) + } + // api.key apiCmd.Flags().String(FlagAPIKey, DefaultAPIKey, DescriptionAPIKey) if err := viper.BindPFlag(ViperAPIKey, apiCmd.Flags().Lookup(FlagAPIKey)); err != nil { From 4ce3d9f60bc3770d40def89d931e81a47682be7a Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 3 Jul 2024 20:51:57 -0400 Subject: [PATCH 46/61] feature(api): permettre d'exposer le serveur API par https Requiert `cfg.API.TLS.Enabled = true` et des fichiers valides pour `cfg.API.TLS.{CertificateFile,PrivateKeyFile}` --- cmd.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cmd.go b/cmd.go index 8c5571d..508af62 100644 --- a/cmd.go +++ b/cmd.go @@ -114,7 +114,22 @@ var apiCmd = &cobra.Command{ */ // Execution - e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.API.Port))) + switch cfg.API.TLS.Enabled { + case false: + e.Logger.Fatal( + e.Start( + fmt.Sprintf(":%d", cfg.API.Port), + ), + ) + case true: + e.Logger.Fatal( + e.StartTLS( + fmt.Sprintf(":%d", cfg.API.Port), + cfg.API.TLS.CertificateFile, + cfg.API.TLS.PrivateKeyFile, + ), + ) + } }, } From a9f16826349aaea3eff018dcb5ca0bc8e2fd0c3f Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 3 Jul 2024 20:53:17 -0400 Subject: [PATCH 47/61] fix(test): ajuster TLS client voki selon config --- client_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client_test.go b/client_test.go index a2aea93..3e1f180 100644 --- a/client_test.go +++ b/client_test.go @@ -22,7 +22,15 @@ func TestAPI(t *testing.T) { httpClient := http.DefaultClient defer httpClient.CloseIdleConnections() - vokiClient := voki.New(httpClient, "localhost", cfg.API.Key, cfg.API.Port, "http") + var protocol string + switch cfg.API.TLS.Enabled { + case true: + protocol = "https" + case false: + protocol = "http" + } + + vokiClient := voki.New(httpClient, "localhost", cfg.API.Key, cfg.API.Port, protocol) apiClient := APIClient{vokiClient} t.Run("get API health", func(t *testing.T) { From 8c074dd443f5e2d43f00c4910d544d9c9738101d Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Sun, 7 Jul 2024 03:58:15 -0400 Subject: [PATCH 48/61] =?UTF-8?q?fix:=20impl=C3=A9menter=20correctement=20?= =?UTF-8?q?tls=20certfile=20et=20keyfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test: ne pas vérifier le certificat avant de l'accepter --- client_test.go | 16 ++++++++++++++-- cmd.go | 7 +++++-- config.go | 38 +++++++++++++++++++------------------- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/client_test.go b/client_test.go index 3e1f180..1593e4c 100644 --- a/client_test.go +++ b/client_test.go @@ -1,6 +1,7 @@ package main import ( + "crypto/tls" "net/http" "testing" @@ -19,7 +20,18 @@ func TestAPI(t *testing.T) { return } - httpClient := http.DefaultClient + //httpClient := http.DefaultClient + //defer httpClient.CloseIdleConnections() + + transport := http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + + httpClient := http.Client{ + Transport: &transport, + } defer httpClient.CloseIdleConnections() var protocol string @@ -30,7 +42,7 @@ func TestAPI(t *testing.T) { protocol = "http" } - vokiClient := voki.New(httpClient, "localhost", cfg.API.Key, cfg.API.Port, protocol) + vokiClient := voki.New(&httpClient, "localhost", cfg.API.Key, cfg.API.Port, protocol) apiClient := APIClient{vokiClient} t.Run("get API health", func(t *testing.T) { diff --git a/cmd.go b/cmd.go index 508af62..1518476 100644 --- a/cmd.go +++ b/cmd.go @@ -122,11 +122,14 @@ var apiCmd = &cobra.Command{ ), ) case true: + //TODO + log.Printf("dbg: certfile='%s' keyfile='%s'", cfg.API.TLS.Certfile, cfg.API.TLS.Keyfile) + e.Logger.Fatal( e.StartTLS( fmt.Sprintf(":%d", cfg.API.Port), - cfg.API.TLS.CertificateFile, - cfg.API.TLS.PrivateKeyFile, + cfg.API.TLS.Certfile, + cfg.API.TLS.Keyfile, ), ) } diff --git a/config.go b/config.go index 927227c..c13b5a0 100644 --- a/config.go +++ b/config.go @@ -16,15 +16,15 @@ const ( DefaultAPITLSEnabled bool = false DescriptionAPITLSEnabled string = "Whether to use TLS or not. Requires certificate and private key files." - ViperAPITLSCertificateFile string = "api.tls.certificate_file" - FlagAPITLSCertificateFile string = "api-tls-certificate-file" - DefaultAPITLSCertificateFile string = "" - DescriptionAPITLSCertificateFile string = "Path to TLS certificate file" + ViperAPITLSCertfile string = "api.tls.certfile" + FlagAPITLSCertfile string = "api-tls-certfile" + DefaultAPITLSCertfile string = "/etc/bottin/cert.pem" + DescriptionAPITLSCertfile string = "Path to TLS certificate file" - ViperAPITLSPrivateKeyFile string = "api.tls.private_key_file" - FlagAPITLSPrivateKeyFile string = "api-tls-private-key-file" - DefaultAPITLSPrivateKeyFile string = "" - DescriptionAPITLSPrivateKeyFile string = "Path to TLS private key file" + ViperAPITLSKeyfile string = "api.tls.keyfile" + FlagAPITLSKeyfile string = "api-tls-keyfile" + DefaultAPITLSKeyfile string = "/etc/bottin/key.pem" + DescriptionAPITLSKeyFile string = "Path to TLS private key file" ViperAPIPort string = "api.port" FlagAPIPort string = "api-port" @@ -108,11 +108,11 @@ type Config struct { Enabled bool `yaml:"enabled"` // Path to file containing TLS certificate - CertificateFile string `yaml:"certificate_file"` + Certfile string `yaml:"certfile"` // Path to file containing TLS private key - PrivateKeyFile string `yaml:"private_key_file"` - } + Keyfile string `yaml:"keyfile"` + } `yaml:"tls"` Port int `yaml:"port"` Key string `yaml:"key"` } `yaml:"api"` @@ -141,8 +141,8 @@ type Config struct { // `Default*` constants defined in this file. func DefaultConfig() (cfg Config) { cfg.API.TLS.Enabled = DefaultAPITLSEnabled - cfg.API.TLS.CertificateFile = DefaultAPITLSCertificateFile - cfg.API.TLS.PrivateKeyFile = DefaultAPITLSPrivateKeyFile + cfg.API.TLS.Certfile = DefaultAPITLSCertfile + cfg.API.TLS.Keyfile = DefaultAPITLSKeyfile cfg.API.Port = DefaultAPIPort cfg.API.Key = DefaultAPIKey cfg.DB.Database = DefaultDBDatabase @@ -178,15 +178,15 @@ func init() { log.Fatal(err) } - // api.tls.certificate_file - apiCmd.Flags().String(FlagAPITLSCertificateFile, DefaultAPITLSCertificateFile, DescriptionAPITLSCertificateFile) - if err := viper.BindPFlag(ViperAPITLSCertificateFile, apiCmd.Flags().Lookup(FlagAPITLSCertificateFile)); err != nil { + // api.tls.certfile + apiCmd.Flags().String(FlagAPITLSCertfile, DefaultAPITLSCertfile, DescriptionAPITLSCertfile) + if err := viper.BindPFlag(ViperAPITLSCertfile, apiCmd.Flags().Lookup(FlagAPITLSCertfile)); err != nil { log.Fatal(err) } - // api.tls.private_key_file - apiCmd.Flags().String(FlagAPITLSPrivateKeyFile, DefaultAPITLSPrivateKeyFile, DescriptionAPITLSPrivateKeyFile) - if err := viper.BindPFlag(ViperAPITLSPrivateKeyFile, apiCmd.Flags().Lookup(FlagAPITLSPrivateKeyFile)); err != nil { + // api.tls.keyfile + apiCmd.Flags().String(FlagAPITLSKeyfile, DefaultAPITLSKeyfile, DescriptionAPITLSKeyFile) + if err := viper.BindPFlag(ViperAPITLSKeyfile, apiCmd.Flags().Lookup(FlagAPITLSKeyfile)); err != nil { log.Fatal(err) } From eb1982898cfb89e5ce74ba7b0c88ec10642d3730 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Mon, 15 Jul 2024 16:52:04 -0400 Subject: [PATCH 49/61] rework: config and cmd Renamed `web` command to `server ui` (web is still an alias to ui) Completely changed the config options and flags Usage of PersistentFlags now allow clearer `--help` BREAKING: cmd modified BREAKING: config overhauled BREAKING: Bump API to v8 --- README.md | 21 +- client_test.go | 10 +- cmd.go | 74 ++--- config.go | 664 ++++++++++++++++++++++++++------------------ docker-compose.yaml | 27 +- go.mod | 2 +- request.go | 18 +- routes.go | 5 +- 8 files changed, 475 insertions(+), 346 deletions(-) diff --git a/README.md b/README.md index acda47f..822c4f1 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,18 @@ https://git.agecem.com/agecem/bottin Remplir .env avec les infos qui seront utilisées pour déployer le container -(Remplacer `bottin` par quelque chose de plus sécuritaire) +Au minimum, il faut ces 3 entrées: + +*Remplacer `bottin` par quelque chose de plus sécuritaire* ```sh -BOTTIN_API_KEY=bottin -BOTTIN_POSTGRES_DATABASE=bottin -BOTTIN_POSTGRES_PASSWORD=bottin -BOTTIN_POSTGRES_USER=bottin -BOTTIN_WEB_PASSWORD=bottin -BOTTIN_WEB_USER=bottin +BOTTIN_SERVER_DB_DATABASE=bottin +BOTTIN_SERVER_DB_PASSWORD=bottin +BOTTIN_SERVER_DB_USER=bottin ``` +*D'autres entrées peuvent être ajoutées, voir `config.go` pour les options* + Déployer avec docker-compose `$ docker-compose up -d` @@ -43,13 +44,13 @@ Pour modifier la configuration du serveur API `$ docker-compose exec -it api vi /etc/bottin/api.yaml` -*Y remplir au minimum le champs `api.key` (string)* +*Y remplir au minimum le champs `server.api.key` (string)* Pour modifier la configuration du client web -`$ docker-compose exec -it web vi /etc/bottin/web.yaml` +`$ docker-compose exec -it ui vi /etc/bottin/ui.yaml` -*Y remplir au minimum les champs `web.api.key` (string), `web.user` (string) et `web.password` (string)* +*Y remplir au minimum les champs `server.ui.api.key` (string), `server.ui.user` (string) et `server.ui.password` (string)* Redémarrer les containers une fois la configuration modifiée diff --git a/client_test.go b/client_test.go index 1593e4c..261eb20 100644 --- a/client_test.go +++ b/client_test.go @@ -34,15 +34,7 @@ func TestAPI(t *testing.T) { } defer httpClient.CloseIdleConnections() - var protocol string - switch cfg.API.TLS.Enabled { - case true: - protocol = "https" - case false: - protocol = "http" - } - - vokiClient := voki.New(&httpClient, "localhost", cfg.API.Key, cfg.API.Port, protocol) + vokiClient := voki.New(&httpClient, "localhost", cfg.Client.API.Key, cfg.Client.API.Port, cfg.Client.API.Protocol) apiClient := APIClient{vokiClient} t.Run("get API health", func(t *testing.T) { diff --git a/cmd.go b/cmd.go index 1518476..8fbd66b 100644 --- a/cmd.go +++ b/cmd.go @@ -32,6 +32,11 @@ func execute() { } } +var serverCmd = &cobra.Command{ + Use: "server", + Short: "Démarrer serveurs (API ou Web UI)", +} + // apiCmd represents the api command var apiCmd = &cobra.Command{ Use: "api", @@ -49,10 +54,12 @@ var apiCmd = &cobra.Command{ e.Pre(middleware.AddTrailingSlash()) - if cfg.API.Key != "" { + if cfg.Server.API.Key != "" { e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { - return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.API.Key)) == 1, nil + return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.Server.API.Key)) == 1, nil })) + } else { + log.Println("Server started but no API key (server.api.key) was provided, using empty key (NOT RECOMMENDED FOR PRODUCTION)") } // DataClient @@ -63,12 +70,12 @@ var apiCmd = &cobra.Command{ ctx, fmt.Sprintf( "user=%s password=%s database=%s host=%s port=%d sslmode=%s ", - cfg.DB.User, - cfg.DB.Password, - cfg.DB.Database, - cfg.DB.Host, - cfg.DB.Port, - cfg.DB.SSLMode, + cfg.Server.API.DB.User, + cfg.Server.API.DB.Password, + cfg.Server.API.DB.Database, + cfg.Server.API.DB.Host, + cfg.Server.API.DB.Port, + cfg.Server.API.DB.SSLMode, )) if err != nil { log.Fatal("init pgx pool:", err) @@ -98,49 +105,50 @@ var apiCmd = &cobra.Command{ /* h := handlers.New(client) - e.GET("/v7/health/", h.GetHealth) + e.GET("/v8/health/", h.GetHealth) - e.POST("/v7/membres/", h.PostMembres) + e.POST("/v8/membres/", h.PostMembres) - e.GET("/v7/membres/", h.ListMembres) + e.GET("/v8/membres/", h.ListMembres) - e.GET("/v7/membres/:membre_id/", h.ReadMembre) + e.GET("/v8/membres/:membre_id/", h.ReadMembre) - e.PUT("/v7/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) + e.PUT("/v8/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) - e.POST("/v7/programmes/", h.PostProgrammes) + e.POST("/v8/programmes/", h.PostProgrammes) - e.POST("/v7/seed/", h.PostSeed) + e.POST("/v8/seed/", h.PostSeed) */ // Execution - switch cfg.API.TLS.Enabled { + switch cfg.Server.API.TLS.Enabled { case false: e.Logger.Fatal( e.Start( - fmt.Sprintf(":%d", cfg.API.Port), + fmt.Sprintf(":%d", cfg.Server.API.Port), ), ) case true: //TODO - log.Printf("dbg: certfile='%s' keyfile='%s'", cfg.API.TLS.Certfile, cfg.API.TLS.Keyfile) + log.Printf("dbg: certfile='%s' keyfile='%s'", cfg.Server.API.TLS.Certfile, cfg.Server.API.TLS.Keyfile) e.Logger.Fatal( e.StartTLS( - fmt.Sprintf(":%d", cfg.API.Port), - cfg.API.TLS.Certfile, - cfg.API.TLS.Keyfile, + fmt.Sprintf(":%d", cfg.Server.API.Port), + cfg.Server.API.TLS.Certfile, + cfg.Server.API.TLS.Keyfile, ), ) } }, } -// webCmd represents the web command -var webCmd = &cobra.Command{ - Use: "web", - Short: "Démarrer le client web", - Args: cobra.ExactArgs(0), +// uiCmd represents the ui command +var uiCmd = &cobra.Command{ + Use: "ui", + Aliases: []string{"web", "interface"}, + Short: "Démarrer l'interface Web UI", + Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { // Parse config var cfg Config @@ -157,8 +165,8 @@ var webCmd = &cobra.Command{ // Auth e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { - usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Web.User)) == 1 - passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Web.Password)) == 1 + usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Server.UI.User)) == 1 + passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Server.UI.Password)) == 1 return usersMatch && passwordsMatch, nil })) @@ -170,10 +178,10 @@ var webCmd = &cobra.Command{ // API Client apiClient := APIClient{voki.New( http.DefaultClient, - cfg.Web.API.Host, - cfg.Web.API.Key, - cfg.Web.API.Port, - cfg.Web.API.Protocol, + cfg.Server.UI.API.Host, + cfg.Server.UI.API.Key, + cfg.Server.UI.API.Port, + cfg.Server.UI.API.Protocol, )} defer apiClient.Voki.CloseIdleConnections() @@ -239,6 +247,6 @@ Programme: [%s] %s // Execution e.Logger.Fatal(e.Start( - fmt.Sprintf(":%d", cfg.Web.Port))) + fmt.Sprintf(":%d", cfg.Server.UI.Port))) }, } diff --git a/config.go b/config.go index c13b5a0..01eecd5 100644 --- a/config.go +++ b/config.go @@ -10,278 +10,52 @@ import ( "github.com/spf13/viper" ) -const ( - ViperAPITLSEnabled string = "api.tls.enabled" - FlagAPITLSEnabled string = "api-tls-enabled" - DefaultAPITLSEnabled bool = false - DescriptionAPITLSEnabled string = "Whether to use TLS or not. Requires certificate and private key files." - - ViperAPITLSCertfile string = "api.tls.certfile" - FlagAPITLSCertfile string = "api-tls-certfile" - DefaultAPITLSCertfile string = "/etc/bottin/cert.pem" - DescriptionAPITLSCertfile string = "Path to TLS certificate file" - - ViperAPITLSKeyfile string = "api.tls.keyfile" - FlagAPITLSKeyfile string = "api-tls-keyfile" - DefaultAPITLSKeyfile string = "/etc/bottin/key.pem" - DescriptionAPITLSKeyFile string = "Path to TLS private key file" - - ViperAPIPort string = "api.port" - FlagAPIPort string = "api-port" - DefaultAPIPort int = 1312 - DescriptionAPIPort string = "API server port" - - ViperAPIKey string = "api.key" - FlagAPIKey string = "api-key" - DefaultAPIKey string = "bottin" - DescriptionAPIKey string = "API server key. Leave empty for no key auth (not recommended)" - - ViperDBDatabase string = "db.database" - FlagDBDatabase string = "db-database" - DefaultDBDatabase string = "bottin" - DescriptionDBDatabase string = "Postgres database" - - ViperDBSSLMode string = "db.sslmode" - FlagDBSSLMode string = "db-sslmode" - DefaultDBSSLMode string = "prefer" - DescriptionDBSSLMode string = "Postgres sslmode" - - ViperDBHost string = "db.host" - FlagDBHost string = "db-host" - DefaultDBHost string = "db" - DescriptionDBHost string = "Postgres host" - - ViperDBPassword string = "db.password" - FlagDBPassword string = "db-password" - DefaultDBPassword string = "bottin" - DescriptionDBPassword string = "Postgres password" - - ViperDBPort string = "db.port" - FlagDBPort string = "db-port" - DefaultDBPort int = 5432 - DescriptionDBPort string = "Postgres port" - - ViperDBUser string = "db.user" - FlagDBUser string = "db-user" - DefaultDBUser string = "bottin" - DescriptionDBUser string = "Postgres user" - - ViperWebUser string = "web.user" - FlagWebUser string = "web-user" - DefaultWebUser string = "bottin" - DescriptionWebUser string = "Web client basic auth user" - - ViperWebPassword string = "web.password" - FlagWebPassword string = "web-password" - DefaultWebPassword string = "bottin" - DescriptionWebPassword string = "Web client basic auth password" - - ViperWebPort string = "web.port" - FlagWebPort string = "web-port" - DefaultWebPort int = 2312 - DescriptionWebPort string = "Web client port" - - ViperWebAPIHost string = "web.api.host" - FlagWebAPIHost string = "web-api-host" - DefaultWebAPIHost string = "api" - DescriptionWebAPIHost string = "Target API server host" - - ViperWebAPIKey string = "web.api.key" - FlagWebAPIKey string = "web-api-key" - DefaultWebAPIKey string = "bottin" - DescriptionWebAPIKey string = "Target API server key" - - ViperWebAPIPort string = "web.api.port" - FlagWebAPIPort string = "web-api-port" - DefaultWebAPIPort int = 1312 - DescriptionWebAPIPort string = "Target API server port" - - ViperWebAPIProtocol string = "web.api.protocol" - FlagWebAPIProtocol string = "web-api-protocol" - DefaultWebAPIProtocol string = "http" - DescriptionWebAPIProtocol string = "Target API server protocol (http/https)" -) - type Config struct { - API struct { - TLS struct { - Enabled bool `yaml:"enabled"` - - // Path to file containing TLS certificate - Certfile string `yaml:"certfile"` - - // Path to file containing TLS private key - Keyfile string `yaml:"keyfile"` - } `yaml:"tls"` - Port int `yaml:"port"` - Key string `yaml:"key"` - } `yaml:"api"` - DB struct { - Database string `yaml:"database"` - Host string `yaml:"host"` - SSLMode string `yaml:"sslmode"` - Password string `yaml:"password"` - Port int `yaml:"port"` - User string `yaml:"user"` - } `yaml:"db"` - Web struct { - User string `yaml:"user"` - Password string `yaml:"password"` - Port int `yaml:"port"` - API struct { + Client struct { + API struct { Host string `yaml:"host"` Key string `yaml:"key"` Port int `yaml:"port"` Protocol string `yaml:"protocol"` } `yaml:"api"` - } `yaml:"web"` -} + } `yaml:"client"` -// DefaultConfig returns a Config filled with the default values from the -// `Default*` constants defined in this file. -func DefaultConfig() (cfg Config) { - cfg.API.TLS.Enabled = DefaultAPITLSEnabled - cfg.API.TLS.Certfile = DefaultAPITLSCertfile - cfg.API.TLS.Keyfile = DefaultAPITLSKeyfile - cfg.API.Port = DefaultAPIPort - cfg.API.Key = DefaultAPIKey - cfg.DB.Database = DefaultDBDatabase - cfg.DB.Host = DefaultDBHost - cfg.DB.SSLMode = DefaultDBSSLMode - cfg.DB.Password = DefaultDBPassword - cfg.DB.Port = DefaultDBPort - cfg.DB.User = DefaultDBUser - cfg.Web.User = DefaultWebUser - cfg.Web.Password = DefaultWebPassword - cfg.Web.Port = DefaultWebPort - cfg.Web.API.Host = DefaultWebAPIHost - cfg.Web.API.Key = DefaultWebAPIKey - cfg.Web.API.Port = DefaultWebAPIPort - cfg.Web.API.Protocol = DefaultWebAPIProtocol - return -} - -func init() { - // rootCmd - - cobra.OnInitialize(initConfig) - - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") - - // apiCmd - - rootCmd.AddCommand(apiCmd) - - // api.tls.enabled - apiCmd.Flags().Bool(FlagAPITLSEnabled, DefaultAPITLSEnabled, DescriptionAPITLSEnabled) - if err := viper.BindPFlag(ViperAPITLSEnabled, apiCmd.Flags().Lookup(FlagAPITLSEnabled)); err != nil { - log.Fatal(err) - } - - // api.tls.certfile - apiCmd.Flags().String(FlagAPITLSCertfile, DefaultAPITLSCertfile, DescriptionAPITLSCertfile) - if err := viper.BindPFlag(ViperAPITLSCertfile, apiCmd.Flags().Lookup(FlagAPITLSCertfile)); err != nil { - log.Fatal(err) - } - - // api.tls.keyfile - apiCmd.Flags().String(FlagAPITLSKeyfile, DefaultAPITLSKeyfile, DescriptionAPITLSKeyFile) - if err := viper.BindPFlag(ViperAPITLSKeyfile, apiCmd.Flags().Lookup(FlagAPITLSKeyfile)); err != nil { - log.Fatal(err) - } - - // api.key - apiCmd.Flags().String(FlagAPIKey, DefaultAPIKey, DescriptionAPIKey) - if err := viper.BindPFlag(ViperAPIKey, apiCmd.Flags().Lookup(FlagAPIKey)); err != nil { - log.Fatal(err) - } - - // api.port - apiCmd.Flags().Int(FlagAPIPort, DefaultAPIPort, DescriptionAPIPort) - if err := viper.BindPFlag(ViperAPIPort, apiCmd.Flags().Lookup(FlagAPIPort)); err != nil { - log.Fatal(err) - } - - // db.database - apiCmd.Flags().String(FlagDBDatabase, DefaultDBDatabase, DescriptionDBDatabase) - if err := viper.BindPFlag(ViperDBDatabase, apiCmd.Flags().Lookup(FlagDBDatabase)); err != nil { - log.Fatal(err) - } - - // db.sslmode - apiCmd.Flags().String(FlagDBSSLMode, DefaultDBSSLMode, DescriptionDBSSLMode) - if err := viper.BindPFlag(ViperDBSSLMode, apiCmd.Flags().Lookup(FlagDBSSLMode)); err != nil { - log.Fatal(err) - } - - // db.host - apiCmd.Flags().String(FlagDBHost, DefaultDBHost, DescriptionDBHost) - if err := viper.BindPFlag(ViperDBHost, apiCmd.Flags().Lookup(FlagDBHost)); err != nil { - log.Fatal(err) - } - - // db.password - apiCmd.Flags().String(FlagDBPassword, DefaultDBPassword, DescriptionDBPassword) - if err := viper.BindPFlag(ViperDBPassword, apiCmd.Flags().Lookup(FlagDBPassword)); err != nil { - log.Fatal(err) - } - - // db.port - apiCmd.Flags().Int(FlagDBPort, DefaultDBPort, DescriptionDBPort) - if err := viper.BindPFlag(ViperDBPort, apiCmd.Flags().Lookup(FlagDBPort)); err != nil { - log.Fatal(err) - } - - // db.user - apiCmd.Flags().String(FlagDBUser, DefaultDBUser, DescriptionDBUser) - if err := viper.BindPFlag(ViperDBUser, apiCmd.Flags().Lookup(FlagDBUser)); err != nil { - log.Fatal(err) - } - - // WebCmd - rootCmd.AddCommand(webCmd) - - // web.api.host - webCmd.Flags().String(FlagWebAPIHost, DefaultWebAPIHost, DescriptionWebAPIHost) - if err := viper.BindPFlag(ViperWebAPIHost, webCmd.Flags().Lookup(FlagWebAPIHost)); err != nil { - log.Fatal(err) - } - - // web.api.key - webCmd.Flags().String(FlagWebAPIKey, DefaultWebAPIKey, DescriptionWebAPIKey) - if err := viper.BindPFlag(ViperWebAPIKey, webCmd.Flags().Lookup(FlagWebAPIKey)); err != nil { - log.Fatal(err) - } - - // web.api.protocol - webCmd.Flags().String(FlagWebAPIProtocol, DefaultWebAPIProtocol, DescriptionWebAPIProtocol) - if err := viper.BindPFlag(ViperWebAPIProtocol, webCmd.Flags().Lookup(FlagWebAPIProtocol)); err != nil { - log.Fatal(err) - } - - // web.api.port - webCmd.Flags().Int(FlagWebAPIPort, DefaultWebAPIPort, DescriptionWebAPIPort) - if err := viper.BindPFlag(ViperWebAPIPort, webCmd.Flags().Lookup(FlagWebAPIPort)); err != nil { - log.Fatal(err) - } - - // web.password - webCmd.Flags().String(FlagWebPassword, DefaultWebPassword, DescriptionWebPassword) - if err := viper.BindPFlag(ViperWebPassword, webCmd.Flags().Lookup(FlagWebPassword)); err != nil { - log.Fatal(err) - } - - // web.port - webCmd.Flags().Int(FlagWebPort, DefaultWebPort, DescriptionWebPort) - if err := viper.BindPFlag(ViperWebPort, webCmd.Flags().Lookup(FlagWebPort)); err != nil { - log.Fatal(err) - } - - // web.user - webCmd.Flags().String(FlagWebUser, DefaultWebUser, DescriptionWebUser) - if err := viper.BindPFlag(ViperWebUser, webCmd.Flags().Lookup(FlagWebUser)); err != nil { - log.Fatal(err) - } + Server struct { + API struct { + DB struct { + Database string `yaml:"database"` + Host string `yaml:"host"` + Password string `yaml:"password"` + Port int `yaml:"port"` + SSLMode string `yaml:"sslmode"` + User string `yaml:"user"` + } `yaml:"db"` + Host string `yaml:"host"` + Key string `yaml:"key"` + Port int `yaml:"port"` + TLS struct { + Enabled bool `yaml:"enabled"` + Certfile string `yaml:"certfile"` + Keyfile string `yaml:"keyfile"` + } `yaml:"tls"` + } `yaml:"api"` + UI struct { + API struct { + Host string `yaml:"host"` + Key string `yaml:"key"` + Port int `yaml:"port"` + Protocol string `yaml:"protocol"` + } `yaml:"api"` + Password string `yaml:"password"` + Port int `yaml:"port"` + TLS struct { + Enabled bool `yaml:"enabled"` + Certfile string `yaml:"certfile"` + Keyfile string `yaml:"keyfile"` + } `yaml:"tls"` + User string `yaml:"user"` + } `yaml:"ui"` + } `yaml:"server"` } var cfgFile string @@ -311,3 +85,363 @@ func initConfig() { fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } + +func init() { + // rootCmd + + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") + + // client.api.host + rootCmd.PersistentFlags().String( + "client-api-host", + "api", + "API server host", + ) + if err := viper.BindPFlag( + "client.api.host", + rootCmd.PersistentFlags().Lookup("client-api-host"), + ); err != nil { + log.Fatal(err) + } + + // client.api.key + rootCmd.PersistentFlags().String( + "client-api-key", + "bottin", + "API server key", + ) + if err := viper.BindPFlag( + "client.api.key", + rootCmd.PersistentFlags().Lookup("client-api-key"), + ); err != nil { + log.Fatal(err) + } + + // client.api.port + rootCmd.PersistentFlags().Int( + "client-api-port", + 1312, + "API server port", + ) + if err := viper.BindPFlag( + "client.api.port", + rootCmd.PersistentFlags().Lookup("client-api-port"), + ); err != nil { + log.Fatal(err) + } + + // client.api.protocol + rootCmd.PersistentFlags().String( + "client-api-protocol", + "https", + "API server protocol", + ) + if err := viper.BindPFlag( + "client.api.protocol", + rootCmd.PersistentFlags().Lookup("client-api-protocol"), + ); err != nil { + log.Fatal(err) + } + + // server + rootCmd.AddCommand(serverCmd) + + // server api + serverCmd.AddCommand(apiCmd) + + // server api db + // server.api.db.database + apiCmd.PersistentFlags().String( + "server-api-db-database", + "bottin", + "Postgres database name", + ) + if err := viper.BindPFlag( + "server.api.db.database", + apiCmd.PersistentFlags().Lookup("server-api-db-database"), + ); err != nil { + log.Fatal(err) + } + + // server.api.db.host + apiCmd.PersistentFlags().String( + "server-api-db-host", + "db", + "Postgres host name", + ) + if err := viper.BindPFlag( + "server.api.db.host", + apiCmd.PersistentFlags().Lookup("server-api-db-host"), + ); err != nil { + log.Fatal(err) + } + + // server.api.db.password + apiCmd.PersistentFlags().String( + "server-api-db-password", + "bottin", + "Postgres password", + ) + if err := viper.BindPFlag( + "server.api.db.password", + apiCmd.PersistentFlags().Lookup("server-api-db-password"), + ); err != nil { + log.Fatal(err) + } + + // server.api.db.port + apiCmd.PersistentFlags().Int( + "server-api-db-port", + 5432, + "Postgres port", + ) + if err := viper.BindPFlag( + "server.api.db.port", + apiCmd.PersistentFlags().Lookup("server-api-db-port"), + ); err != nil { + log.Fatal(err) + } + + // server.api.db.sslmode + apiCmd.PersistentFlags().String( + "server-api-db-sslmode", + "prefer", + "Postgres sslmode", + ) + if err := viper.BindPFlag( + "server.api.db.sslmode", + apiCmd.PersistentFlags().Lookup("server-api-db-sslmode"), + ); err != nil { + log.Fatal(err) + } + + // server.api.db.user + apiCmd.PersistentFlags().String( + "server-api-db-user", + "bottin", + "Postgres user name", + ) + if err := viper.BindPFlag( + "server.api.db.user", + apiCmd.PersistentFlags().Lookup("server-api-db-user"), + ); err != nil { + log.Fatal(err) + } + + // server.api.host + apiCmd.PersistentFlags().String( + "server-api-host", + "", + "API server hostname or IP to answer on (empty = any)", + ) + if err := viper.BindPFlag( + "server.api.host", + apiCmd.PersistentFlags().Lookup("server-api-host"), + ); err != nil { + log.Fatal(err) + } + + // server.api.key + apiCmd.PersistentFlags().String( + "server-api-key", + "bottin", + "API server key", + ) + if err := viper.BindPFlag( + "server.api.key", + apiCmd.PersistentFlags().Lookup("server-api-key"), + ); err != nil { + log.Fatal(err) + } + + // server.api.port + apiCmd.PersistentFlags().Int( + "server-api-port", + 1312, + "API server port", + ) + if err := viper.BindPFlag( + "server.api.port", + apiCmd.PersistentFlags().Lookup("server-api-port"), + ); err != nil { + log.Fatal(err) + } + + // server api tls + // server.api.tls.enabled + apiCmd.PersistentFlags().Bool( + "server-api-tls-enabled", + true, + "Use TLS for API server connections (requires certfile and keyfile)", + ) + if err := viper.BindPFlag( + "server.api.tls.enabled", + apiCmd.PersistentFlags().Lookup("server-api-tls-enabled"), + ); err != nil { + log.Fatal(err) + } + + // server.api.tls.certfile + apiCmd.PersistentFlags().String( + "server-api-tls-certfile", + "/etc/bottin/cert.pem", + "Path to certificate file", + ) + if err := viper.BindPFlag( + "server.api.tls.certfile", + apiCmd.PersistentFlags().Lookup("server-api-tls-certfile"), + ); err != nil { + log.Fatal(err) + } + + // server.api.tls.keyfile + apiCmd.PersistentFlags().String( + "server-api-tls-keyfile", + "/etc/bottin/key.pem", + "Path to private key file", + ) + if err := viper.BindPFlag( + "server.api.tls.keyfile", + apiCmd.PersistentFlags().Lookup("server-api-tls-keyfile"), + ); err != nil { + log.Fatal(err) + } + + // server ui + serverCmd.AddCommand(uiCmd) + + // server ui api + + // server.ui.api.host + uiCmd.PersistentFlags().String( + "server-ui-api-host", + "api", + "Web UI backend API server host name", + ) + if err := viper.BindPFlag( + "server.ui.api.host", + uiCmd.PersistentFlags().Lookup("server-ui-api-host"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.api.key + uiCmd.PersistentFlags().String( + "server-ui-api-key", + "bottin", + "Web UI backend API server key", + ) + if err := viper.BindPFlag( + "server.ui.api.key", + uiCmd.PersistentFlags().Lookup("server-ui-api-key"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.api.port + uiCmd.PersistentFlags().Int( + "server-ui-api-port", + 1312, + "Web UI backend API server port", + ) + if err := viper.BindPFlag( + "server.ui.api.port", + uiCmd.PersistentFlags().Lookup("server-ui-api-port"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.api.protocol + uiCmd.PersistentFlags().String( + "server-ui-api-protocol", + "https", + "Web UI backend API server protocol", + ) + if err := viper.BindPFlag( + "server.ui.api.protocol", + uiCmd.PersistentFlags().Lookup("server-ui-api-protocol"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.password + uiCmd.PersistentFlags().String( + "server-ui-password", + "bottin", + "Web UI password", + ) + if err := viper.BindPFlag( + "server.ui.password", + uiCmd.PersistentFlags().Lookup("server-ui-password"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.port + uiCmd.PersistentFlags().Int( + "server-ui-port", + 2312, + "Web UI port", + ) + if err := viper.BindPFlag( + "server.ui.port", + uiCmd.PersistentFlags().Lookup("server-ui-port"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.user + uiCmd.PersistentFlags().String( + "server-ui-user", + "bottin", + "Web UI user", + ) + if err := viper.BindPFlag( + "server.ui.user", + uiCmd.PersistentFlags().Lookup("server-ui-user"), + ); err != nil { + log.Fatal(err) + } + + // server ui tls + // server.ui.tls.enabled + uiCmd.PersistentFlags().Bool( + "server-ui-tls-enabled", + true, + "Web UI enable TLS (requires certfile and keyfile)", + ) + if err := viper.BindPFlag( + "server.ui.tls.enabled", + uiCmd.PersistentFlags().Lookup("server-ui-tls-enabled"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.tls.certfile + uiCmd.PersistentFlags().String( + "server-ui-tls-certfile", + "/etc/bottin/cert.pem", + "Path to Web UI TLS certificate file", + ) + if err := viper.BindPFlag( + "server.ui.tls.certfile", + uiCmd.PersistentFlags().Lookup("server-ui-tls-certfile"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.tls.keyfile + uiCmd.PersistentFlags().String( + "server-ui-tls-keyfile", + "/etc/bottin/key.pem", + "Path to Web UI TLS private key file", + ) + if err := viper.BindPFlag( + "server.ui.tls.keyfile", + uiCmd.PersistentFlags().Lookup("server-ui-tls-keyfile"), + ); err != nil { + log.Fatal(err) + } +} diff --git a/docker-compose.yaml b/docker-compose.yaml index 9f2e604..1156be7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,9 +3,9 @@ services: db: image: 'docker.io/library/postgres:16' environment: - POSTGRES_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" - POSTGRES_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}" - POSTGRES_USER: "${BOTTIN_POSTGRES_USER}" + POSTGRES_DATABASE: "${BOTTIN_SERVER_API_DB_DATABASE}" + POSTGRES_PASSWORD: "${BOTTIN_SERVER_API_DB_PASSWORD}" + POSTGRES_USER: "${BOTTIN_SERVER_API_DB_USER}" volumes: - 'db-data:/var/lib/postgresql/data' restart: 'unless-stopped' @@ -15,33 +15,26 @@ services: - db build: . image: 'git.agecem.com/agecem/bottin:latest' - environment: - BOTTIN_DB_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" - BOTTIN_DB_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}" - BOTTIN_DB_USER: "${BOTTIN_POSTGRES_USER}" - BOTTIN_API_KEY: "${BOTTIN_API_KEY}" + env_file: '.env' ports: - '1312:1312' volumes: - 'api-config:/etc/bottin' restart: 'unless-stopped' - command: ['bottin', '--config', '/etc/bottin/api.yaml', 'api'] + command: ['bottin', '--config', '/etc/bottin/api.yaml', 'server', 'api'] - web: + ui: depends_on: - api build: . image: 'git.agecem.com/agecem/bottin:latest' - environment: - BOTTIN_WEB_API_KEY: "${BOTTIN_API_KEY}" - BOTTIN_WEB_PASSWORD: "${BOTTIN_WEB_PASSWORD}" - BOTTIN_WEB_USER: "${BOTTIN_WEB_USER}" + env_file: '.env' ports: - '2312:2312' volumes: - - 'web-config:/etc/bottin' + - 'ui-config:/etc/bottin' restart: 'unless-stopped' - command: ['bottin', '--config', '/etc/bottin/web.yaml', 'web'] + command: ['bottin', '--config', '/etc/bottin/ui.yaml', 'server', 'ui'] # adminer: # image: adminer @@ -54,4 +47,4 @@ services: volumes: db-data: api-config: - web-config: + ui-config: diff --git a/go.mod b/go.mod index 2c52fee..489b37d 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.agecem.com/agecem/bottin/v7 +module git.agecem.com/agecem/bottin/v8 go 1.22.0 diff --git a/request.go b/request.go index 8f47f10..f1a73b4 100644 --- a/request.go +++ b/request.go @@ -23,7 +23,7 @@ func (request HealthGETRequest) Request(v *voki.Voki) (response HealthGETRespons statusCode, body, err := v.CallAndParse( http.MethodGet, - "/api/v7/health/", + "/api/v8/health/", nil, true, ) @@ -64,7 +64,7 @@ func (request ProgrammesPOSTRequest) Request(v *voki.Voki) (response ProgrammesP statusCode, body, err := v.CallAndParse( http.MethodPost, - "/api/v7/programme/", + "/api/v8/programme/", &buf, true, ) @@ -105,7 +105,7 @@ func (request MembresPOSTRequest) Request(v *voki.Voki) (response MembresPOSTRes statusCode, body, err := v.CallAndParse( http.MethodPost, - "/api/v7/membre/", + "/api/v8/membre/", &buf, true, ) @@ -146,7 +146,7 @@ func (request MembreGETRequest) Request(v *voki.Voki) (response MembreGETRespons statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v7/membre/%s/", request.Param.MembreID), + fmt.Sprintf("/api/v8/membre/%s/", request.Param.MembreID), nil, true, ) @@ -180,7 +180,7 @@ func (request MembresGETRequest) Request(v *voki.Voki) (response MembresGETRespo statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v7/membre/?limit=%d", request.Query.Limit), + fmt.Sprintf("/api/v8/membre/?limit=%d", request.Query.Limit), nil, true, ) @@ -224,7 +224,7 @@ func (request MembrePreferedNamePUTRequest) Request(v *voki.Voki) (response Memb statusCode, body, err := v.CallAndParse( http.MethodPut, - fmt.Sprintf("/api/v7/membre/%s/prefered_name/", request.Param.MembreID), + fmt.Sprintf("/api/v8/membre/%s/prefered_name/", request.Param.MembreID), &buf, true, ) @@ -258,7 +258,7 @@ func (request ProgrammesGETRequest) Request(v *voki.Voki) (response ProgrammesGE statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v7/programme/?limit=%d", request.Query.Limit), + fmt.Sprintf("/api/v8/programme/?limit=%d", request.Query.Limit), nil, true, ) @@ -292,7 +292,7 @@ func (request MembresDisplayGETRequest) Request(v *voki.Voki) (response MembresD statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v7/membre/display/?limit=%d", request.Query.Limit), + fmt.Sprintf("/api/v8/membre/display/?limit=%d", request.Query.Limit), nil, true, ) @@ -333,7 +333,7 @@ func (request MembreDisplayGETRequest) Request(v *voki.Voki) (response MembreDis statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v7/membre/%s/display/", request.Param.MembreID), + fmt.Sprintf("/api/v8/membre/%s/display/", request.Param.MembreID), nil, true, ) diff --git a/routes.go b/routes.go index ca7dcb2..a631aba 100644 --- a/routes.go +++ b/routes.go @@ -17,7 +17,7 @@ func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error { _ = db _ = cfg - apiPath := "/api/v7" + apiPath := "/api/v8" apiGroup := e.Group(apiPath) p := pave.New() if err := pave.EchoRegister[HealthGETRequest]( @@ -248,7 +248,8 @@ func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error { } } else { - //TODO cfg.API.DefaultLimit + //TODO cfg.Server.API.DefaultLimit + //TODO cfg.Client.API.Limit request.Query.Limit = 1000 } From 03c9ad5f3c52a98d65f830cfd51d401457587c2c Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 23 Jul 2024 11:40:40 -0400 Subject: [PATCH 50/61] =?UTF-8?q?fix:=20v=C3=A9rifier=20existence=20de=20c?= =?UTF-8?q?ertfile=20et=20keyfile=20pour=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Au lieu de print leur valeur à l'écran --- cmd.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd.go b/cmd.go index 8fbd66b..3fe7a06 100644 --- a/cmd.go +++ b/cmd.go @@ -129,8 +129,13 @@ var apiCmd = &cobra.Command{ ), ) case true: - //TODO - log.Printf("dbg: certfile='%s' keyfile='%s'", cfg.Server.API.TLS.Certfile, cfg.Server.API.TLS.Keyfile) + if cfg.Server.API.TLS.Certfile == "" { + log.Fatal("TLS enabled for API but no certificate file provided") + } + + if cfg.Server.API.TLS.Keyfile == "" { + log.Fatal("TLS enabled for UI but no private key file provided") + } e.Logger.Fatal( e.StartTLS( From 8a9decfe6cd705e22c4f0630056f7992800374d1 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 23 Jul 2024 11:44:41 -0400 Subject: [PATCH 51/61] =?UTF-8?q?fix:=20consid=C3=A9rer=20cfg.Server.API.H?= =?UTF-8?q?ost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Valeur n'avait aucun effet précédemment, permet maintenant de choisir sur quel hôte le serveur API est rejoignable --- cmd.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd.go b/cmd.go index 3fe7a06..a477a8a 100644 --- a/cmd.go +++ b/cmd.go @@ -125,7 +125,7 @@ var apiCmd = &cobra.Command{ case false: e.Logger.Fatal( e.Start( - fmt.Sprintf(":%d", cfg.Server.API.Port), + fmt.Sprintf("%s:%d", cfg.Server.API.Host, cfg.Server.API.Port), ), ) case true: @@ -139,7 +139,7 @@ var apiCmd = &cobra.Command{ e.Logger.Fatal( e.StartTLS( - fmt.Sprintf(":%d", cfg.Server.API.Port), + fmt.Sprintf("%s:%d", cfg.Server.API.Host, cfg.Server.API.Port), cfg.Server.API.TLS.Certfile, cfg.Server.API.TLS.Keyfile, ), From f5aa25a12a4ab7724736d8f4b3fe1b46ba823dfd Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 23 Jul 2024 11:46:37 -0400 Subject: [PATCH 52/61] =?UTF-8?q?feature:=20ajouter=20cfg.Server.UI.Host?= =?UTF-8?q?=20et=20impl=C3=A9menter=20UI=20TLS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd.go | 24 ++++++++++++++++++++++-- config.go | 14 ++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/cmd.go b/cmd.go index a477a8a..a54cff6 100644 --- a/cmd.go +++ b/cmd.go @@ -251,7 +251,27 @@ Programme: [%s] %s }) // Execution - e.Logger.Fatal(e.Start( - fmt.Sprintf(":%d", cfg.Server.UI.Port))) + switch cfg.Server.UI.TLS.Enabled { + case false: + e.Logger.Fatal(e.Start( + fmt.Sprintf("%s:%d", cfg.Server.UI.Host, cfg.Server.UI.Port))) + case true: + if cfg.Server.UI.TLS.Certfile == "" { + log.Fatal("TLS enabled for UI but no certificate file provided") + } + + if cfg.Server.UI.TLS.Keyfile == "" { + log.Fatal("TLS enabled for UI but no private key file provided") + } + + e.Logger.Fatal( + e.StartTLS( + fmt.Sprintf("%s:%d", cfg.Server.UI.Host, cfg.Server.UI.Port), + cfg.Server.UI.TLS.Certfile, + cfg.Server.UI.TLS.Keyfile, + ), + ) + } + }, } diff --git a/config.go b/config.go index 01eecd5..c611e07 100644 --- a/config.go +++ b/config.go @@ -46,6 +46,7 @@ type Config struct { Port int `yaml:"port"` Protocol string `yaml:"protocol"` } `yaml:"api"` + Host string `yaml:"host"` Password string `yaml:"password"` Port int `yaml:"port"` TLS struct { @@ -366,6 +367,19 @@ func init() { log.Fatal(err) } + // server.ui.host + uiCmd.PersistentFlags().String( + "server-ui-host", + "", + "Web UI host", + ) + if err := viper.BindPFlag( + "server.ui.host", + uiCmd.PersistentFlags().Lookup("server-ui-host"), + ); err != nil { + log.Fatal(err) + } + // server.ui.password uiCmd.PersistentFlags().String( "server-ui-password", From 7ddf89a859a8ebd23defbe582eb635026cdba790 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 3 Sep 2024 16:42:05 -0400 Subject: [PATCH 53/61] feature(config): add `server.ui.api.tls.skipverify` --- config.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/config.go b/config.go index c611e07..cf521a7 100644 --- a/config.go +++ b/config.go @@ -45,6 +45,9 @@ type Config struct { Key string `yaml:"key"` Port int `yaml:"port"` Protocol string `yaml:"protocol"` + TLS struct { + SkipVerify bool `yaml:"skipverify"` + } `yaml:"tls"` } `yaml:"api"` Host string `yaml:"host"` Password string `yaml:"password"` @@ -367,6 +370,19 @@ func init() { log.Fatal(err) } + // server.ui.api.tls.skipverify + uiCmd.PersistentFlags().Bool( + "server-ui-api-tls-skipverify", + false, + "Skip API server TLS certificate verification", + ) + if err := viper.BindPFlag( + "server.ui.api.tls.skipverify", + uiCmd.PersistentFlags().Lookup("server-ui-api-tls-skipverify"), + ); err != nil { + log.Fatal(err) + } + // server.ui.host uiCmd.PersistentFlags().String( "server-ui-host", From 2b6c631d64b07554f64ead5c6b57b9c46e876202 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 3 Sep 2024 16:42:25 -0400 Subject: [PATCH 54/61] fix(compose): adjust `.env` inject --- docker-compose.yaml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 1156be7..4def68e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,9 +3,9 @@ services: db: image: 'docker.io/library/postgres:16' environment: - POSTGRES_DATABASE: "${BOTTIN_SERVER_API_DB_DATABASE}" - POSTGRES_PASSWORD: "${BOTTIN_SERVER_API_DB_PASSWORD}" - POSTGRES_USER: "${BOTTIN_SERVER_API_DB_USER}" + POSTGRES_DATABASE: "${BOTTIN_SERVER_API_DB_DATABASE:-bottin}" + POSTGRES_PASSWORD: "${BOTTIN_SERVER_API_DB_PASSWORD:-bottin}" + POSTGRES_USER: "${BOTTIN_SERVER_API_DB_USER:-bottin}" volumes: - 'db-data:/var/lib/postgresql/data' restart: 'unless-stopped' @@ -15,7 +15,13 @@ services: - db build: . image: 'git.agecem.com/agecem/bottin:latest' - env_file: '.env' + env: + BOTTIN_SERVER_API_DB_DATABASE: "${BOTTIN_SERVER_API_DB_DATABASE:-bottin}" + BOTTIN_SERVER_API_DB_HOST: "${BOTTIN_SERVER_API_DB_HOST:-db}" + BOTTIN_SERVER_API_DB_PASSWORD: "${BOTTIN_SERVER_API_DB_PASSWORD:-bottin}" + BOTTIN_SERVER_API_DB_USER: "${BOTTIN_SERVER_API_DB_USER:-bottin}" + #BOTTIN_SERVER_API_HOST: "${BOTTIN_SERVER_API_HOST:}" + #BOTTIN_SERVER_API_KEY: "${BOTTIN_SERVER_API_KEY ports: - '1312:1312' volumes: @@ -28,7 +34,9 @@ services: - api build: . image: 'git.agecem.com/agecem/bottin:latest' - env_file: '.env' + env: + BOTTIN_WEB_PASSWORD: "${BOTTIN_WEB_PASSWORD:-bottin}" + BOTTIN_WEB_USER: "${BOTTIN_WEB_USER:-bottin}" ports: - '2312:2312' volumes: From 9072f7114ac46551484ab74b79e2a82238b01605 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 3 Sep 2024 16:43:22 -0400 Subject: [PATCH 55/61] =?UTF-8?q?feature(cmd):=20impl=C3=A9menter=20UI=20A?= =?UTF-8?q?PI=20TLS=20skip=20verify?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd.go b/cmd.go index a54cff6..e801dd3 100644 --- a/cmd.go +++ b/cmd.go @@ -3,6 +3,7 @@ package main import ( "context" "crypto/subtle" + "crypto/tls" "fmt" "html/template" "log" @@ -181,8 +182,15 @@ var uiCmd = &cobra.Command{ } // API Client + var httpClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: cfg.Server.UI.API.TLS.SkipVerify, + }, + }, + } apiClient := APIClient{voki.New( - http.DefaultClient, + httpClient, cfg.Server.UI.API.Host, cfg.Server.UI.API.Key, cfg.Server.UI.API.Port, From bdff81c6b23bbe2eaa40d889fe2ec0b6915a1443 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 3 Sep 2024 16:58:25 -0400 Subject: [PATCH 56/61] =?UTF-8?q?feature(compose):=20directly=20inject=20.?= =?UTF-8?q?env=20file=20=C3=A0=20containers=20api=20et=20ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yaml | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 4def68e..5487eb1 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,9 +3,9 @@ services: db: image: 'docker.io/library/postgres:16' environment: - POSTGRES_DATABASE: "${BOTTIN_SERVER_API_DB_DATABASE:-bottin}" - POSTGRES_PASSWORD: "${BOTTIN_SERVER_API_DB_PASSWORD:-bottin}" - POSTGRES_USER: "${BOTTIN_SERVER_API_DB_USER:-bottin}" + POSTGRES_DATABASE: "${BOTTIN_SERVER_API_DB_DATABASE:?}" + POSTGRES_PASSWORD: "${BOTTIN_SERVER_API_DB_PASSWORD:?}" + POSTGRES_USER: "${BOTTIN_SERVER_API_DB_USER:?}" volumes: - 'db-data:/var/lib/postgresql/data' restart: 'unless-stopped' @@ -15,13 +15,7 @@ services: - db build: . image: 'git.agecem.com/agecem/bottin:latest' - env: - BOTTIN_SERVER_API_DB_DATABASE: "${BOTTIN_SERVER_API_DB_DATABASE:-bottin}" - BOTTIN_SERVER_API_DB_HOST: "${BOTTIN_SERVER_API_DB_HOST:-db}" - BOTTIN_SERVER_API_DB_PASSWORD: "${BOTTIN_SERVER_API_DB_PASSWORD:-bottin}" - BOTTIN_SERVER_API_DB_USER: "${BOTTIN_SERVER_API_DB_USER:-bottin}" - #BOTTIN_SERVER_API_HOST: "${BOTTIN_SERVER_API_HOST:}" - #BOTTIN_SERVER_API_KEY: "${BOTTIN_SERVER_API_KEY + env_file: '.env' ports: - '1312:1312' volumes: @@ -34,9 +28,7 @@ services: - api build: . image: 'git.agecem.com/agecem/bottin:latest' - env: - BOTTIN_WEB_PASSWORD: "${BOTTIN_WEB_PASSWORD:-bottin}" - BOTTIN_WEB_USER: "${BOTTIN_WEB_USER:-bottin}" + env_file: '.env' ports: - '2312:2312' volumes: From c9a45f8db868ecc429cda87a5eb7209c7bfeb32d Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 3 Sep 2024 17:01:34 -0400 Subject: [PATCH 57/61] chores(Dockerfile): bump golang -> 1.23.0 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6479581..8d7516d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22.3 as build +FROM golang:1.23.0 as build LABEL author="vlbeaudoin" From 882553521a8551a2d9c6f578375cc497a1506088 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 3 Sep 2024 17:02:20 -0400 Subject: [PATCH 58/61] chores(Dockerfile): bump alpine -> 3.20.3 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6479581..462394a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN CGO_ENABLED=0 go build -a -o bottin . # Alpine -FROM alpine:3.20 +FROM alpine:3.20.3 WORKDIR /app From 0640395fd2ac905f61208ddf9fb7667aae8c59f6 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 3 Sep 2024 17:06:10 -0400 Subject: [PATCH 59/61] fix(Dockerfile): utiliser version de alpine qui existe actually --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e08a2f7..18bfd2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN CGO_ENABLED=0 go build -a -o bottin . # Alpine -FROM alpine:3.20.3 +FROM alpine:3.20.2 WORKDIR /app From b419a5b26045474e6b9bd4349e5134b247c47c22 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 18 Sep 2024 19:06:33 -0400 Subject: [PATCH 60/61] =?UTF-8?q?major:=20s=C3=A9parer=20commande=20de=20l?= =?UTF-8?q?ibrairie=20importable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump major version à 9 package main déplacé vers cmd/bottin/ pour garder `go install` qui nomme l'exécutable `bottin`, sans empêcher d'importer le code à l'extérieur du projet avec pkg/bottin/. Déplacer fichiers SQL vers queries/ Déplacer fichiers html vers templates/ Ajouter scripts/ avec génération et injection de certificats x509 (https) et les ajouter au Makefile Ajouter début d'exemple de manifests dans deployments/kubernetes/ --- .gitignore | 2 + Dockerfile | 8 +- Makefile | 12 + README.md | 58 +-- cmd.go | 285 --------------- config.go => cmd/bottin/main.go | 344 +++++++++++++++--- client_test.go => cmd/bottin/main_test.go | 13 +- docker-compose.yaml => compose.yaml | 5 +- deployments/kubernetes/bottin-pod.yaml | 60 +++ .../kubernetes/example-bottin-secret.yaml | 26 ++ go.mod | 2 +- main.go | 18 - client.go => pkg/bottin/client.go | 2 +- pkg/bottin/config.go | 53 +++ db.go => pkg/bottin/db.go | 13 +- entity.go => pkg/bottin/entity.go | 2 +- request.go => pkg/bottin/request.go | 20 +- response.go => pkg/bottin/response.go | 2 +- routes.go => pkg/bottin/routes.go | 6 +- queries/queries.go | 14 + {sql => queries}/schema.sql | 0 {sql => queries}/views.sql | 0 scripts/compose-inject-x509.sh | 6 + scripts/generate-self-signed-x509.sh | 2 + template.go => templates/templates.go | 11 +- 25 files changed, 513 insertions(+), 451 deletions(-) delete mode 100644 cmd.go rename config.go => cmd/bottin/main.go (56%) rename client_test.go => cmd/bottin/main_test.go (91%) rename docker-compose.yaml => compose.yaml (95%) create mode 100644 deployments/kubernetes/bottin-pod.yaml create mode 100644 deployments/kubernetes/example-bottin-secret.yaml delete mode 100644 main.go rename client.go => pkg/bottin/client.go (99%) create mode 100644 pkg/bottin/config.go rename db.go => pkg/bottin/db.go (97%) rename entity.go => pkg/bottin/entity.go (98%) rename request.go => pkg/bottin/request.go (94%) rename response.go => pkg/bottin/response.go (99%) rename routes.go => pkg/bottin/routes.go (99%) create mode 100644 queries/queries.go rename {sql => queries}/schema.sql (100%) rename {sql => queries}/views.sql (100%) create mode 100755 scripts/compose-inject-x509.sh create mode 100755 scripts/generate-self-signed-x509.sh rename template.go => templates/templates.go (55%) diff --git a/.gitignore b/.gitignore index e2f7237..a8c4332 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ # Dependency directories (remove the comment below to include it) # vendor/ +# cert files +*.pem # env .env diff --git a/Dockerfile b/Dockerfile index 18bfd2d..d24bb50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,14 @@ LABEL author="vlbeaudoin" WORKDIR /go/src/app -COPY go.mod go.sum client.go client_test.go cmd.go config.go db.go entity.go main.go request.go response.go routes.go template.go ./ +COPY go.mod go.sum LICENSE ./ -ADD sql/ sql/ +ADD cmd/ cmd/ +ADD pkg/ pkg/ +ADD queries/ queries/ ADD templates/ templates/ -RUN CGO_ENABLED=0 go build -a -o bottin . +RUN CGO_ENABLED=0 go build -a -o bottin ./cmd/bottin # Alpine diff --git a/Makefile b/Makefile index 31a6c23..827fc0c 100644 --- a/Makefile +++ b/Makefile @@ -14,3 +14,15 @@ help: ## Show this help .PHONY: test-integration test-integration: ## run integration tests through API client. Config is read from `~/.bottin.yaml`. WARNING: affects data in the database, do not run on production server docker-compose down && docker-compose up -d --build && sleep 2 && go test + +.PHONY: dev +dev: generate-self-signed-x509 compose-inject-x509 ## deploy development environment on docker-compose + docker-compose up -d + +.PHONY: generate-self-signed-x509 +generate-self-signed-x509: ## Générer une paire de clés x509 self-signed pour utilisation avec un serveur de développement + ./scripts/generate-self-signed-x509.sh + +.PHONY: compose-inject-x509 +compose-inject-x509: ## Copie la paire de clés x509 du current directory vers les containers orchestrés par docker-compose + ./scripts/compose-inject-x509.sh diff --git a/README.md b/README.md index 822c4f1..a024676 100644 --- a/README.md +++ b/README.md @@ -1,57 +1 @@ -# agecem/bottin - -Bottin de la masse étudiante, en Go - -https://git.agecem.com/agecem/bottin - -## fonctionalités - -### Serveur API - -- Insertion de membre et programme -- Lecture de membre -- Modification du nom d'usage de membre - -### Client web - -- Lecture de membre par requête au serveur API - -## usage - -Remplir .env avec les infos qui seront utilisées pour déployer le container - -Au minimum, il faut ces 3 entrées: - -*Remplacer `bottin` par quelque chose de plus sécuritaire* - -```sh -BOTTIN_SERVER_DB_DATABASE=bottin -BOTTIN_SERVER_DB_PASSWORD=bottin -BOTTIN_SERVER_DB_USER=bottin -``` - -*D'autres entrées peuvent être ajoutées, voir `config.go` pour les options* - -Déployer avec docker-compose - -`$ docker-compose up -d` - -### Optionnel: configuration par fichiers YAML - -*seulement nécessaire si les fichiers `.env` et `docker-compose.yaml` ne contiennent pas toute l'information nécessaire* - -Pour modifier la configuration du serveur API - -`$ docker-compose exec -it api vi /etc/bottin/api.yaml` - -*Y remplir au minimum le champs `server.api.key` (string)* - -Pour modifier la configuration du client web - -`$ docker-compose exec -it ui vi /etc/bottin/ui.yaml` - -*Y remplir au minimum les champs `server.ui.api.key` (string), `server.ui.user` (string) et `server.ui.password` (string)* - -Redémarrer les containers une fois la configuration modifiée - -`$ docker-compose down && docker-compose up -d` +Requiert un fichier .env ici pour un déploiement avec base de donnée diff --git a/cmd.go b/cmd.go deleted file mode 100644 index e801dd3..0000000 --- a/cmd.go +++ /dev/null @@ -1,285 +0,0 @@ -package main - -import ( - "context" - "crypto/subtle" - "crypto/tls" - "fmt" - "html/template" - "log" - "net/http" - "os" - - "codeberg.org/vlbeaudoin/voki/v3" - "github.com/jackc/pgx/v5/pgxpool" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "bottin", - Short: "Bottin étudiant de l'AGECEM", -} - -// execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func execute() { - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} - -var serverCmd = &cobra.Command{ - Use: "server", - Short: "Démarrer serveurs (API ou Web UI)", -} - -// apiCmd represents the api command -var apiCmd = &cobra.Command{ - Use: "api", - Short: "Démarrer le serveur API", - Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, args []string) { - var cfg Config - if err := viper.Unmarshal(&cfg); err != nil { - log.Fatal("parse config:", err) - } - - e := echo.New() - - // Middlewares - - e.Pre(middleware.AddTrailingSlash()) - - if cfg.Server.API.Key != "" { - e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { - return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.Server.API.Key)) == 1, nil - })) - } else { - log.Println("Server started but no API key (server.api.key) was provided, using empty key (NOT RECOMMENDED FOR PRODUCTION)") - } - - // DataClient - ctx := context.Background() - - //prep - pool, err := pgxpool.New( - ctx, - fmt.Sprintf( - "user=%s password=%s database=%s host=%s port=%d sslmode=%s ", - cfg.Server.API.DB.User, - cfg.Server.API.DB.Password, - cfg.Server.API.DB.Database, - cfg.Server.API.DB.Host, - cfg.Server.API.DB.Port, - cfg.Server.API.DB.SSLMode, - )) - if err != nil { - log.Fatal("init pgx pool:", err) - } - defer pool.Close() - - db := &PostgresClient{ - Ctx: ctx, - Pool: pool, - } - if err := db.Pool.Ping(ctx); err != nil { - log.Fatal("ping db:", err) - } - - if err := db.CreateOrReplaceSchema(); err != nil { - log.Fatal("create or replace schema:", err) - } - - if err := db.CreateOrReplaceViews(); err != nil { - log.Fatal("create or replace views:", err) - } - - // Routes - if err := addRoutes(e, db, cfg); err != nil { - log.Fatal("add routes:", err) - } - /* - h := handlers.New(client) - - e.GET("/v8/health/", h.GetHealth) - - e.POST("/v8/membres/", h.PostMembres) - - e.GET("/v8/membres/", h.ListMembres) - - e.GET("/v8/membres/:membre_id/", h.ReadMembre) - - e.PUT("/v8/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) - - e.POST("/v8/programmes/", h.PostProgrammes) - - e.POST("/v8/seed/", h.PostSeed) - */ - - // Execution - switch cfg.Server.API.TLS.Enabled { - case false: - e.Logger.Fatal( - e.Start( - fmt.Sprintf("%s:%d", cfg.Server.API.Host, cfg.Server.API.Port), - ), - ) - case true: - if cfg.Server.API.TLS.Certfile == "" { - log.Fatal("TLS enabled for API but no certificate file provided") - } - - if cfg.Server.API.TLS.Keyfile == "" { - log.Fatal("TLS enabled for UI but no private key file provided") - } - - e.Logger.Fatal( - e.StartTLS( - fmt.Sprintf("%s:%d", cfg.Server.API.Host, cfg.Server.API.Port), - cfg.Server.API.TLS.Certfile, - cfg.Server.API.TLS.Keyfile, - ), - ) - } - }, -} - -// uiCmd represents the ui command -var uiCmd = &cobra.Command{ - Use: "ui", - Aliases: []string{"web", "interface"}, - Short: "Démarrer l'interface Web UI", - Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, args []string) { - // Parse config - var cfg Config - if err := viper.Unmarshal(&cfg); err != nil { - log.Fatal("init config:", err) - } - - e := echo.New() - - // Middlewares - - // Trailing slash - e.Pre(middleware.AddTrailingSlash()) - - // Auth - e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { - usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Server.UI.User)) == 1 - passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Server.UI.Password)) == 1 - return usersMatch && passwordsMatch, nil - })) - - // Templating - e.Renderer = &Template{ - templates: template.Must(template.ParseFS(templatesFS, "templates/*.html")), - } - - // API Client - var httpClient = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: cfg.Server.UI.API.TLS.SkipVerify, - }, - }, - } - apiClient := APIClient{voki.New( - httpClient, - cfg.Server.UI.API.Host, - cfg.Server.UI.API.Key, - cfg.Server.UI.API.Port, - cfg.Server.UI.API.Protocol, - )} - defer apiClient.Voki.CloseIdleConnections() - - // Routes - e.GET("/", func(c echo.Context) error { - pingResult, err := apiClient.GetHealth() - if err != nil { - return c.Render( - http.StatusOK, - "index-html", - voki.MessageResponse{Message: fmt.Sprintf("impossible d'accéder au serveur API: %s", err)}, - ) - } - - return c.Render( - http.StatusOK, - "index-html", - voki.MessageResponse{Message: pingResult}, - ) - }) - - e.GET("/membre/", func(c echo.Context) error { - membreID := c.QueryParam("membre_id") - switch { - case membreID == "": - return c.Render( - http.StatusOK, - "index-html", - voki.MessageResponse{Message: "❗Veuillez entrer un numéro étudiant à rechercher"}, - ) - case !IsMembreID(membreID): - return c.Render( - http.StatusOK, - "index-html", - voki.MessageResponse{Message: fmt.Sprintf("❗Numéro étudiant '%s' invalide", membreID)}, - ) - } - - membre, err := apiClient.GetMembreForDisplay(membreID) - if err != nil { - return c.Render( - http.StatusOK, - "index-html", - voki.MessageResponse{Message: fmt.Sprintf("❗erreur: %s", err)}, - ) - } - - return c.Render( - http.StatusOK, - "index-html", - voki.MessageResponse{Message: fmt.Sprintf(` -Numéro étudiant: %s -Nom d'usage: %s -Programme: [%s] %s -`, - membre.ID, - membre.Name, - membre.ProgrammeID, - membre.ProgrammeName, - )}, - ) - }) - - // Execution - switch cfg.Server.UI.TLS.Enabled { - case false: - e.Logger.Fatal(e.Start( - fmt.Sprintf("%s:%d", cfg.Server.UI.Host, cfg.Server.UI.Port))) - case true: - if cfg.Server.UI.TLS.Certfile == "" { - log.Fatal("TLS enabled for UI but no certificate file provided") - } - - if cfg.Server.UI.TLS.Keyfile == "" { - log.Fatal("TLS enabled for UI but no private key file provided") - } - - e.Logger.Fatal( - e.StartTLS( - fmt.Sprintf("%s:%d", cfg.Server.UI.Host, cfg.Server.UI.Port), - cfg.Server.UI.TLS.Certfile, - cfg.Server.UI.TLS.Keyfile, - ), - ) - } - - }, -} diff --git a/config.go b/cmd/bottin/main.go similarity index 56% rename from config.go rename to cmd/bottin/main.go index cf521a7..526a344 100644 --- a/config.go +++ b/cmd/bottin/main.go @@ -1,67 +1,25 @@ package main import ( + "context" + "crypto/subtle" + "crypto/tls" "fmt" "log" + "net/http" "os" "strings" + "codeberg.org/vlbeaudoin/voki/v3" + "git.agecem.com/agecem/bottin/v9/pkg/bottin" + "git.agecem.com/agecem/bottin/v9/templates" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" "github.com/spf13/cobra" "github.com/spf13/viper" ) -type Config struct { - Client struct { - API struct { - Host string `yaml:"host"` - Key string `yaml:"key"` - Port int `yaml:"port"` - Protocol string `yaml:"protocol"` - } `yaml:"api"` - } `yaml:"client"` - - Server struct { - API struct { - DB struct { - Database string `yaml:"database"` - Host string `yaml:"host"` - Password string `yaml:"password"` - Port int `yaml:"port"` - SSLMode string `yaml:"sslmode"` - User string `yaml:"user"` - } `yaml:"db"` - Host string `yaml:"host"` - Key string `yaml:"key"` - Port int `yaml:"port"` - TLS struct { - Enabled bool `yaml:"enabled"` - Certfile string `yaml:"certfile"` - Keyfile string `yaml:"keyfile"` - } `yaml:"tls"` - } `yaml:"api"` - UI struct { - API struct { - Host string `yaml:"host"` - Key string `yaml:"key"` - Port int `yaml:"port"` - Protocol string `yaml:"protocol"` - TLS struct { - SkipVerify bool `yaml:"skipverify"` - } `yaml:"tls"` - } `yaml:"api"` - Host string `yaml:"host"` - Password string `yaml:"password"` - Port int `yaml:"port"` - TLS struct { - Enabled bool `yaml:"enabled"` - Certfile string `yaml:"certfile"` - Keyfile string `yaml:"keyfile"` - } `yaml:"tls"` - User string `yaml:"user"` - } `yaml:"ui"` - } `yaml:"server"` -} - var cfgFile string // initConfig reads in config file and ENV variables if set. @@ -90,6 +48,17 @@ func initConfig() { } } +func main() { + /* TODO + if err := Run(context.Background(), Config{}, nil, os.Stdout); err != nil { + log.Fatal(err) + } + */ + + // Handle the command-line via cobra and viper + execute() +} + func init() { // rootCmd @@ -475,3 +444,274 @@ func init() { log.Fatal(err) } } + +/* TODO +func Run(ctx context.Context, config Config, args []string, stdout io.Writer) error { + return fmt.Errorf("not implemented") +} +*/ + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "bottin", + Short: "Bottin étudiant de l'AGECEM", +} + +// execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +var serverCmd = &cobra.Command{ + Use: "server", + Short: "Démarrer serveurs (API ou Web UI)", +} + +// apiCmd represents the api command +var apiCmd = &cobra.Command{ + Use: "api", + Short: "Démarrer le serveur API", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + var cfg bottin.Config + if err := viper.Unmarshal(&cfg); err != nil { + log.Fatal("parse config:", err) + } + + e := echo.New() + + // Middlewares + + e.Pre(middleware.AddTrailingSlash()) + + if cfg.Server.API.Key != "" { + e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { + return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.Server.API.Key)) == 1, nil + })) + } else { + log.Println("Server started but no API key (server.api.key) was provided, using empty key (NOT RECOMMENDED FOR PRODUCTION)") + } + + // DataClient + ctx := context.Background() + + //prep + pool, err := pgxpool.New( + ctx, + fmt.Sprintf( + "user=%s password=%s database=%s host=%s port=%d sslmode=%s ", + cfg.Server.API.DB.User, + cfg.Server.API.DB.Password, + cfg.Server.API.DB.Database, + cfg.Server.API.DB.Host, + cfg.Server.API.DB.Port, + cfg.Server.API.DB.SSLMode, + )) + if err != nil { + log.Fatal("init pgx pool:", err) + } + defer pool.Close() + + db := &bottin.PostgresClient{ + Ctx: ctx, + Pool: pool, + } + if err := db.Pool.Ping(ctx); err != nil { + log.Fatal("ping db:", err) + } + + if err := db.CreateOrReplaceSchema(); err != nil { + log.Fatal("create or replace schema:", err) + } + + if err := db.CreateOrReplaceViews(); err != nil { + log.Fatal("create or replace views:", err) + } + + // Routes + if err := bottin.AddRoutes(e, db, cfg); err != nil { + log.Fatal("add routes:", err) + } + /* + h := handlers.New(client) + + e.GET("/v9/health/", h.GetHealth) + + e.POST("/v9/membres/", h.PostMembres) + + e.GET("/v9/membres/", h.ListMembres) + + e.GET("/v9/membres/:membre_id/", h.ReadMembre) + + e.PUT("/v9/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) + + e.POST("/v9/programmes/", h.PostProgrammes) + + e.POST("/v9/seed/", h.PostSeed) + */ + + // Execution + switch cfg.Server.API.TLS.Enabled { + case false: + e.Logger.Fatal( + e.Start( + fmt.Sprintf("%s:%d", cfg.Server.API.Host, cfg.Server.API.Port), + ), + ) + case true: + if cfg.Server.API.TLS.Certfile == "" { + log.Fatal("TLS enabled for API but no certificate file provided") + } + + if cfg.Server.API.TLS.Keyfile == "" { + log.Fatal("TLS enabled for UI but no private key file provided") + } + + e.Logger.Fatal( + e.StartTLS( + fmt.Sprintf("%s:%d", cfg.Server.API.Host, cfg.Server.API.Port), + cfg.Server.API.TLS.Certfile, + cfg.Server.API.TLS.Keyfile, + ), + ) + } + }, +} + +// uiCmd represents the ui command +var uiCmd = &cobra.Command{ + Use: "ui", + Aliases: []string{"web", "interface"}, + Short: "Démarrer l'interface Web UI", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + // Parse config + var cfg bottin.Config + if err := viper.Unmarshal(&cfg); err != nil { + log.Fatal("init config:", err) + } + + e := echo.New() + + // Middlewares + + // Trailing slash + e.Pre(middleware.AddTrailingSlash()) + + // Auth + e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { + usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Server.UI.User)) == 1 + passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Server.UI.Password)) == 1 + return usersMatch && passwordsMatch, nil + })) + + // Templating + e.Renderer = templates.NewTemplate() + + // API Client + var httpClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: cfg.Server.UI.API.TLS.SkipVerify, + }, + }, + } + apiClient := bottin.APIClient{ + Voki: voki.New( + httpClient, + cfg.Server.UI.API.Host, + cfg.Server.UI.API.Key, + cfg.Server.UI.API.Port, + cfg.Server.UI.API.Protocol, + )} + defer apiClient.Voki.CloseIdleConnections() + + // Routes + e.GET("/", func(c echo.Context) error { + pingResult, err := apiClient.GetHealth() + if err != nil { + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: fmt.Sprintf("impossible d'accéder au serveur API: %s", err)}, + ) + } + + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: pingResult}, + ) + }) + + e.GET("/membre/", func(c echo.Context) error { + membreID := c.QueryParam("membre_id") + switch { + case membreID == "": + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: "❗Veuillez entrer un numéro étudiant à rechercher"}, + ) + case !bottin.IsMembreID(membreID): + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: fmt.Sprintf("❗Numéro étudiant '%s' invalide", membreID)}, + ) + } + + membre, err := apiClient.GetMembreForDisplay(membreID) + if err != nil { + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: fmt.Sprintf("❗erreur: %s", err)}, + ) + } + + return c.Render( + http.StatusOK, + "index-html", + voki.MessageResponse{Message: fmt.Sprintf(` +Numéro étudiant: %s +Nom d'usage: %s +Programme: [%s] %s +`, + membre.ID, + membre.Name, + membre.ProgrammeID, + membre.ProgrammeName, + )}, + ) + }) + + // Execution + switch cfg.Server.UI.TLS.Enabled { + case false: + e.Logger.Fatal(e.Start( + fmt.Sprintf("%s:%d", cfg.Server.UI.Host, cfg.Server.UI.Port))) + case true: + if cfg.Server.UI.TLS.Certfile == "" { + log.Fatal("TLS enabled for UI but no certificate file provided") + } + + if cfg.Server.UI.TLS.Keyfile == "" { + log.Fatal("TLS enabled for UI but no private key file provided") + } + + e.Logger.Fatal( + e.StartTLS( + fmt.Sprintf("%s:%d", cfg.Server.UI.Host, cfg.Server.UI.Port), + cfg.Server.UI.TLS.Certfile, + cfg.Server.UI.TLS.Keyfile, + ), + ) + } + + }, +} diff --git a/client_test.go b/cmd/bottin/main_test.go similarity index 91% rename from client_test.go rename to cmd/bottin/main_test.go index 261eb20..f8b6685 100644 --- a/client_test.go +++ b/cmd/bottin/main_test.go @@ -6,6 +6,7 @@ import ( "testing" "codeberg.org/vlbeaudoin/voki/v3" + "git.agecem.com/agecem/bottin/v9/pkg/bottin" "github.com/spf13/viper" ) @@ -14,7 +15,7 @@ func init() { } func TestAPI(t *testing.T) { - var cfg Config + var cfg bottin.Config if err := viper.Unmarshal(&cfg); err != nil { t.Error(err) return @@ -35,7 +36,7 @@ func TestAPI(t *testing.T) { defer httpClient.CloseIdleConnections() vokiClient := voki.New(&httpClient, "localhost", cfg.Client.API.Key, cfg.Client.API.Port, cfg.Client.API.Protocol) - apiClient := APIClient{vokiClient} + apiClient := bottin.APIClient{Voki: vokiClient} t.Run("get API health", func(t *testing.T) { health, err := apiClient.GetHealth() @@ -53,9 +54,9 @@ func TestAPI(t *testing.T) { t.Run("insert programmes", func(t *testing.T) { - programmes := []Programme{ - {"404.42", "Cool programme"}, - {"200.10", "Autre programme"}, + programmes := []bottin.Programme{ + {ID: "404.42", Name: "Cool programme"}, + {ID: "200.10", Name: "Autre programme"}, } t.Log("programmes:", programmes) _, err := apiClient.InsertProgrammes(programmes...) @@ -64,7 +65,7 @@ func TestAPI(t *testing.T) { } }) - testMembres := []Membre{ + testMembres := []bottin.Membre{ { ID: "0000000", FirstName: "Test", diff --git a/docker-compose.yaml b/compose.yaml similarity index 95% rename from docker-compose.yaml rename to compose.yaml index 5487eb1..686312c 100644 --- a/docker-compose.yaml +++ b/compose.yaml @@ -1,3 +1,4 @@ +name: 'bottin' services: db: @@ -13,7 +14,7 @@ services: api: depends_on: - db - build: . + build: ../.. image: 'git.agecem.com/agecem/bottin:latest' env_file: '.env' ports: @@ -26,7 +27,7 @@ services: ui: depends_on: - api - build: . + build: ../.. image: 'git.agecem.com/agecem/bottin:latest' env_file: '.env' ports: diff --git a/deployments/kubernetes/bottin-pod.yaml b/deployments/kubernetes/bottin-pod.yaml new file mode 100644 index 0000000..72569e4 --- /dev/null +++ b/deployments/kubernetes/bottin-pod.yaml @@ -0,0 +1,60 @@ +apiVersion: v1 +kind: Pod +metadata: + name: bottin-pod +spec: + initContainers: + - name: clone + image: alpine:3.20 + command: ['sh', '-c'] + args: + - apk add git && + git clone -- https://git.agecem.com/agecem/bottin /opt/bottin-src + volumeMounts: + - name: bottin-src + mountPath: /opt/bottin-src + - name: build + image: golang:1.23 + env: + - name: CGO_ENABLED + value: '0' + command: ['sh', '-c'] + args: + - cd /opt/bottin-src && + go build -a -o /opt/bottin-executable/bottin + volumeMounts: + - name: bottin-src + mountPath: /opt/bottin-src + - name: bottin-executable + mountPath: /opt/bottin-executable + containers: + - name: api + image: alpine:3.20 + command: ['sh', '-c'] + args: + - ln -s /opt/bottin-executable/bottin /usr/bin/bottin + volumeMounts: + - name: bottin-executable + mountPath: /opt/bottin-executable + - name: bottin-secret + readOnly: true + mountPath: '/etc/bottin' + - name: ui + image: alpine:3.20 + command: ['sh', '-c'] + args: + - bottin --config /etc/bottin/ui.yaml server ui + volumeMounts: + - name: bottin-executable + mountPath: /opt/bottin-executable + - name: bottin-secret + readOnly: true + mountPath: '/etc/bottin' + volumes: + - name: bottin-src + emptyDir: {} + - name: bottin-executable + emptyDir: {} + - name: bottin-secret + secret: + secretName: bottin-secret diff --git a/deployments/kubernetes/example-bottin-secret.yaml b/deployments/kubernetes/example-bottin-secret.yaml new file mode 100644 index 0000000..36bed97 --- /dev/null +++ b/deployments/kubernetes/example-bottin-secret.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Secret +metadata: + name: bottin-secret +stringData: + api.yaml: | + bottin: + server: + api: + db: + database: 'bottin' + host: 'db.example.com' + password: 'bottin' + sslmode: 'require' + user: 'bottin' + key: 'bottin' + ui.yaml: | + bottin: + server: + ui: + api: + tls: + skipverify: 'true' + key: 'bottin' + password: 'bottin' + user: 'bottin' diff --git a/go.mod b/go.mod index 489b37d..e3da402 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.agecem.com/agecem/bottin/v8 +module git.agecem.com/agecem/bottin/v9 go 1.22.0 diff --git a/main.go b/main.go deleted file mode 100644 index 6f81738..0000000 --- a/main.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -func main() { - /* TODO - if err := Run(context.Background(), Config{}, nil, os.Stdout); err != nil { - log.Fatal(err) - } - */ - - // Handle the command-line via cobra and viper - execute() -} - -/* TODO -func Run(ctx context.Context, config Config, args []string, stdout io.Writer) error { - return fmt.Errorf("not implemented") -} -*/ diff --git a/client.go b/pkg/bottin/client.go similarity index 99% rename from client.go rename to pkg/bottin/client.go index e4958a6..529d9c8 100644 --- a/client.go +++ b/pkg/bottin/client.go @@ -1,4 +1,4 @@ -package main +package bottin import ( "fmt" diff --git a/pkg/bottin/config.go b/pkg/bottin/config.go new file mode 100644 index 0000000..b0c4650 --- /dev/null +++ b/pkg/bottin/config.go @@ -0,0 +1,53 @@ +package bottin + +type Config struct { + Client struct { + API struct { + Host string `yaml:"host"` + Key string `yaml:"key"` + Port int `yaml:"port"` + Protocol string `yaml:"protocol"` + } `yaml:"api"` + } `yaml:"client"` + + Server struct { + API struct { + DB struct { + Database string `yaml:"database"` + Host string `yaml:"host"` + Password string `yaml:"password"` + Port int `yaml:"port"` + SSLMode string `yaml:"sslmode"` + User string `yaml:"user"` + } `yaml:"db"` + Host string `yaml:"host"` + Key string `yaml:"key"` + Port int `yaml:"port"` + TLS struct { + Enabled bool `yaml:"enabled"` + Certfile string `yaml:"certfile"` + Keyfile string `yaml:"keyfile"` + } `yaml:"tls"` + } `yaml:"api"` + UI struct { + API struct { + Host string `yaml:"host"` + Key string `yaml:"key"` + Port int `yaml:"port"` + Protocol string `yaml:"protocol"` + TLS struct { + SkipVerify bool `yaml:"skipverify"` + } `yaml:"tls"` + } `yaml:"api"` + Host string `yaml:"host"` + Password string `yaml:"password"` + Port int `yaml:"port"` + TLS struct { + Enabled bool `yaml:"enabled"` + Certfile string `yaml:"certfile"` + Keyfile string `yaml:"keyfile"` + } `yaml:"tls"` + User string `yaml:"user"` + } `yaml:"ui"` + } `yaml:"server"` +} diff --git a/db.go b/pkg/bottin/db.go similarity index 97% rename from db.go rename to pkg/bottin/db.go index f0b2f4e..0769fbc 100644 --- a/db.go +++ b/pkg/bottin/db.go @@ -1,20 +1,15 @@ -package main +package bottin import ( "context" _ "embed" "fmt" + "git.agecem.com/agecem/bottin/v9/queries" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) -//go:embed sql/schema.sql -var sqlSchema string - -//go:embed sql/views.sql -var sqlViews string - type PostgresClient struct { //TODO move context out of client Ctx context.Context @@ -22,12 +17,12 @@ type PostgresClient struct { } func (db *PostgresClient) CreateOrReplaceSchema() error { - _, err := db.Pool.Exec(db.Ctx, sqlSchema) + _, err := db.Pool.Exec(db.Ctx, queries.SQLSchema()) return err } func (db *PostgresClient) CreateOrReplaceViews() error { - _, err := db.Pool.Exec(db.Ctx, sqlViews) + _, err := db.Pool.Exec(db.Ctx, queries.SQLViews()) return err } diff --git a/entity.go b/pkg/bottin/entity.go similarity index 98% rename from entity.go rename to pkg/bottin/entity.go index 672f54c..8279b9e 100644 --- a/entity.go +++ b/pkg/bottin/entity.go @@ -1,4 +1,4 @@ -package main +package bottin import "unicode" diff --git a/request.go b/pkg/bottin/request.go similarity index 94% rename from request.go rename to pkg/bottin/request.go index f1a73b4..555ddab 100644 --- a/request.go +++ b/pkg/bottin/request.go @@ -1,4 +1,4 @@ -package main +package bottin import ( "bytes" @@ -23,7 +23,7 @@ func (request HealthGETRequest) Request(v *voki.Voki) (response HealthGETRespons statusCode, body, err := v.CallAndParse( http.MethodGet, - "/api/v8/health/", + "/api/v9/health/", nil, true, ) @@ -64,7 +64,7 @@ func (request ProgrammesPOSTRequest) Request(v *voki.Voki) (response ProgrammesP statusCode, body, err := v.CallAndParse( http.MethodPost, - "/api/v8/programme/", + "/api/v9/programme/", &buf, true, ) @@ -105,7 +105,7 @@ func (request MembresPOSTRequest) Request(v *voki.Voki) (response MembresPOSTRes statusCode, body, err := v.CallAndParse( http.MethodPost, - "/api/v8/membre/", + "/api/v9/membre/", &buf, true, ) @@ -146,7 +146,7 @@ func (request MembreGETRequest) Request(v *voki.Voki) (response MembreGETRespons statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v8/membre/%s/", request.Param.MembreID), + fmt.Sprintf("/api/v9/membre/%s/", request.Param.MembreID), nil, true, ) @@ -180,7 +180,7 @@ func (request MembresGETRequest) Request(v *voki.Voki) (response MembresGETRespo statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v8/membre/?limit=%d", request.Query.Limit), + fmt.Sprintf("/api/v9/membre/?limit=%d", request.Query.Limit), nil, true, ) @@ -224,7 +224,7 @@ func (request MembrePreferedNamePUTRequest) Request(v *voki.Voki) (response Memb statusCode, body, err := v.CallAndParse( http.MethodPut, - fmt.Sprintf("/api/v8/membre/%s/prefered_name/", request.Param.MembreID), + fmt.Sprintf("/api/v9/membre/%s/prefered_name/", request.Param.MembreID), &buf, true, ) @@ -258,7 +258,7 @@ func (request ProgrammesGETRequest) Request(v *voki.Voki) (response ProgrammesGE statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v8/programme/?limit=%d", request.Query.Limit), + fmt.Sprintf("/api/v9/programme/?limit=%d", request.Query.Limit), nil, true, ) @@ -292,7 +292,7 @@ func (request MembresDisplayGETRequest) Request(v *voki.Voki) (response MembresD statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v8/membre/display/?limit=%d", request.Query.Limit), + fmt.Sprintf("/api/v9/membre/display/?limit=%d", request.Query.Limit), nil, true, ) @@ -333,7 +333,7 @@ func (request MembreDisplayGETRequest) Request(v *voki.Voki) (response MembreDis statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v8/membre/%s/display/", request.Param.MembreID), + fmt.Sprintf("/api/v9/membre/%s/display/", request.Param.MembreID), nil, true, ) diff --git a/response.go b/pkg/bottin/response.go similarity index 99% rename from response.go rename to pkg/bottin/response.go index 19818df..cd034f1 100644 --- a/response.go +++ b/pkg/bottin/response.go @@ -1,4 +1,4 @@ -package main +package bottin import ( "fmt" diff --git a/routes.go b/pkg/bottin/routes.go similarity index 99% rename from routes.go rename to pkg/bottin/routes.go index a631aba..dc97041 100644 --- a/routes.go +++ b/pkg/bottin/routes.go @@ -1,4 +1,4 @@ -package main +package bottin import ( "encoding/csv" @@ -13,11 +13,11 @@ import ( "github.com/labstack/echo/v4" ) -func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error { +func AddRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error { _ = db _ = cfg - apiPath := "/api/v8" + apiPath := "/api/v9" apiGroup := e.Group(apiPath) p := pave.New() if err := pave.EchoRegister[HealthGETRequest]( diff --git a/queries/queries.go b/queries/queries.go new file mode 100644 index 0000000..0fa44d8 --- /dev/null +++ b/queries/queries.go @@ -0,0 +1,14 @@ +package queries + +import ( + _ "embed" +) + +//go:embed schema.sql +var sqlSchema string + +//go:embed views.sql +var sqlViews string + +func SQLSchema() string { return sqlSchema } +func SQLViews() string { return sqlViews } diff --git a/sql/schema.sql b/queries/schema.sql similarity index 100% rename from sql/schema.sql rename to queries/schema.sql diff --git a/sql/views.sql b/queries/views.sql similarity index 100% rename from sql/views.sql rename to queries/views.sql diff --git a/scripts/compose-inject-x509.sh b/scripts/compose-inject-x509.sh new file mode 100755 index 0000000..42ed90c --- /dev/null +++ b/scripts/compose-inject-x509.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +docker-compose cp cert.pem api:/etc/bottin/cert.pem +docker-compose cp key.pem api:/etc/bottin/key.pem +docker-compose cp cert.pem ui:/etc/bottin/cert.pem +docker-compose cp key.pem ui:/etc/bottin/key.pem diff --git a/scripts/generate-self-signed-x509.sh b/scripts/generate-self-signed-x509.sh new file mode 100755 index 0000000..2e1a5bc --- /dev/null +++ b/scripts/generate-self-signed-x509.sh @@ -0,0 +1,2 @@ +#!/bin/sh +openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes diff --git a/template.go b/templates/templates.go similarity index 55% rename from template.go rename to templates/templates.go index a5b4980..2539cde 100644 --- a/template.go +++ b/templates/templates.go @@ -1,4 +1,4 @@ -package main +package templates import ( "embed" @@ -8,7 +8,7 @@ import ( "github.com/labstack/echo/v4" ) -//go:embed templates/* +//go:embed *.html var templatesFS embed.FS type Template struct { @@ -18,3 +18,10 @@ type Template struct { func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { return t.templates.ExecuteTemplate(w, name, data) } + +// NewTemplate returns a new Template instance with templates embedded from *.html +func NewTemplate() *Template { + return &Template{ + templates: template.Must(template.ParseFS(templatesFS, "*.html")), + } +} From 1ad0d614776e110d73b1466843a5b313b2897512 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 19 Sep 2024 14:41:24 -0400 Subject: [PATCH 61/61] docs: rapporter ancien README.md --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a024676..bf96431 100644 --- a/README.md +++ b/README.md @@ -1 +1,58 @@ -Requiert un fichier .env ici pour un déploiement avec base de donnée +# agecem/bottin + +Bottin de la masse étudiante, en Go + +https://git.agecem.com/agecem/bottin + +## fonctionalités + +### Serveur API + +- Insertion de membre et programme +- Lecture de membre +- Modification du nom d'usage de membre + +### Client web + +- Lecture de membre par requête au serveur API + +## usage + +Remplir .env avec les infos qui seront utilisées pour déployer le container + +Au minimum, il faut ces 3 entrées: + +*Remplacer `bottin` par quelque chose de plus sécuritaire* + +```sh +BOTTIN_SERVER_DB_DATABASE=bottin +BOTTIN_SERVER_DB_PASSWORD=bottin +BOTTIN_SERVER_DB_USER=bottin +``` + +*D'autres entrées peuvent être ajoutées, voir `config.go` pour les options* + +Déployer avec docker-compose + +`$ docker-compose up -d` + +### Optionnel: configuration par fichiers YAML + +*seulement nécessaire si les fichiers `.env` et `docker-compose.yaml` ne contiennent pas toute l'information nécessaire* + +Pour modifier la configuration du serveur API + +`$ docker-compose exec -it api vi /etc/bottin/api.yaml` + +*Y remplir au minimum le champs `server.api.key` (string)* + +Pour modifier la configuration du client web + +`$ docker-compose exec -it ui vi /etc/bottin/ui.yaml` + +*Y remplir au minimum les champs `server.ui.api.key` (string), `server.ui.user` (string) et `server.ui.password` (string)* + +Redémarrer les containers une fois la configuration modifiée + +`$ docker-compose down && docker-compose up -d` +v