2023-03-28 02:35:42 -04:00
/ *
Copyright © 2023 AGECEM
* /
package cmd
2023-02-17 17:28:47 -05:00
import (
2023-04-21 21:34:18 -04:00
"crypto/subtle"
2023-03-21 20:29:06 -04:00
"fmt"
2023-04-21 21:34:18 -04:00
"log"
2023-03-28 02:35:42 -04:00
"embed"
2023-03-21 18:37:51 -04:00
"html/template"
"io"
2023-02-17 17:28:47 -05:00
"net/http"
2023-11-20 15:13:42 -05:00
"codeberg.org/vlbeaudoin/pave"
2023-10-17 17:30:37 -04:00
"codeberg.org/vlbeaudoin/serpents"
2023-03-28 02:35:42 -04:00
"github.com/spf13/cobra"
2023-04-21 16:41:50 -04:00
"github.com/spf13/viper"
2023-03-28 02:35:42 -04:00
2023-08-30 15:24:37 -04:00
"git.agecem.com/agecem/agecem-org/api"
2023-11-20 14:14:00 -05:00
"git.agecem.com/agecem/agecem-org/apihandler"
2023-11-20 15:13:42 -05:00
"git.agecem.com/agecem/agecem-org/apirequest"
"git.agecem.com/agecem/agecem-org/apiresponse"
2023-07-04 16:05:23 -04:00
"git.agecem.com/agecem/agecem-org/config"
2023-07-04 16:47:03 -04:00
"git.agecem.com/agecem/agecem-org/media"
2023-03-21 18:37:51 -04:00
"git.agecem.com/agecem/agecem-org/public"
2023-07-04 21:57:13 -04:00
"git.agecem.com/agecem/agecem-org/templates"
2023-11-20 14:19:59 -05:00
"git.agecem.com/agecem/agecem-org/webhandler"
2023-02-17 17:28:47 -05:00
"github.com/labstack/echo/v4"
2023-03-21 18:37:51 -04:00
"github.com/labstack/echo/v4/middleware"
2023-02-17 17:28:47 -05:00
)
2023-03-21 18:37:51 -04:00
type Template struct {
templates * template . Template
}
2023-07-04 16:05:23 -04:00
var cfg config . Config
2023-07-04 21:57:13 -04:00
var (
publicFS embed . FS
templatesFS embed . FS
)
2023-03-21 18:37:51 -04:00
2023-03-28 02:35:42 -04:00
// serverCmd represents the server command
var serverCmd = & cobra . Command {
Use : "server" ,
Short : "Démarrer le serveur web" ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
2023-07-04 16:05:23 -04:00
if err := viper . Unmarshal ( & cfg ) ; err != nil {
log . Fatal ( err )
}
2023-07-04 20:35:13 -04:00
mediaClient , err := media . NewMediaClientFromViper ( )
2023-10-05 14:11:48 -04:00
switch err != nil {
case true :
log . Printf ( "media.NewMediaClientFromViper error: %s" , err )
case false :
new_buckets , err := mediaClient . Seed ( )
if err != nil {
log . Printf ( "(*media.MediaClient).Seed error: %s" , err )
} else {
log . Printf ( "Seeded %d buckets.\n" , len ( new_buckets ) )
}
2023-07-04 20:35:13 -04:00
}
2023-03-28 02:35:42 -04:00
RunServer ( )
} ,
}
2023-02-23 04:24:04 -05:00
func init ( ) {
2023-03-28 02:35:42 -04:00
rootCmd . AddCommand ( serverCmd )
2023-07-04 21:57:13 -04:00
publicFS = public . GetPublicFS ( )
templatesFS = templates . GetTemplatesFS ( )
2023-04-21 16:41:50 -04:00
2023-10-17 17:30:37 -04:00
serpents . Int ( serverCmd . Flags ( ) ,
"server.port" , "server-port" , 8080 ,
"Port to run the webserver on" )
2023-04-21 16:41:50 -04:00
2023-04-28 20:00:54 -04:00
// Not currently used
/ *
// server.documents.location - --server-documents-location
serverCmd . Flags ( ) . String ( "server-documents-location" , "us-east" , "Storage bucket location (config: server.documents.location)" )
viper . BindPFlag ( "server.documents.location" , serverCmd . Flags ( ) . Lookup ( "server-documents-location" ) )
* /
2023-04-21 16:41:50 -04:00
2023-10-17 17:30:37 -04:00
serpents . String ( serverCmd . Flags ( ) ,
"server.documents.endpoint" , "server-documents-endpoint" , "minio:9000" ,
"Storage server endpoint" )
serpents . String ( serverCmd . Flags ( ) ,
"server.documents.access_key_id" , "server-documents-access-key-id" , "agecem-org" ,
"Storage server access key id" )
serpents . String ( serverCmd . Flags ( ) ,
"server.documents.secret_access_key" , "server-documents-secret-access-key" , "agecem-org" ,
"Storage server secret access key" )
serpents . Bool ( serverCmd . Flags ( ) ,
"server.documents.use_ssl" , "server-documents-use-ssl" , false ,
"Storage server SSL status" )
serpents . StringToString ( serverCmd . Flags ( ) ,
"server.documents.buckets" , "server-documents-buckets" , map [ string ] string {
"proces-verbaux" : "Procès-verbaux" ,
"politiques" : "Politiques" ,
"reglements" : "Règlements" ,
"formulaires" : "Formulaires" ,
} ,
"Buckets that are allowed to be accessed by the API" )
serpents . Bool ( serverCmd . Flags ( ) ,
"server.api.auth" , "server-api-auth" , true ,
"Enable to allow key authentication for /v1 routes" )
serpents . String ( serverCmd . Flags ( ) ,
"server.api.key" , "server-api-key" , "agecem-org" ,
"Key to use for authenticating to /v1 routes" )
serpents . Int ( serverCmd . Flags ( ) ,
"server.api.port" , "server-api-port" , 8080 ,
"API server port" )
serpents . String ( serverCmd . Flags ( ) ,
"server.api.protocol" , "server-api-protocol" , "http" ,
"API server protocol (http/https)" )
serpents . String ( serverCmd . Flags ( ) ,
"server.api.host" , "server-api-host" , "localhost" ,
"API server host" )
serpents . Bool ( serverCmd . Flags ( ) ,
"server.admin.auth" , "server-admin-auth" , true ,
"Enable to allow basic authentication for /admin routes" )
serpents . String ( serverCmd . Flags ( ) ,
"server.admin.username" , "server-admin-username" , "agecem-org" ,
"Username for basic authentication for /admin routes" )
serpents . String ( serverCmd . Flags ( ) ,
"server.admin.password" , "server-admin-password" , "agecem-org" ,
"Password for basic authentication for /admin routes" )
2023-02-23 04:24:04 -05:00
}
2023-03-28 02:35:42 -04:00
func RunServer ( ) {
2023-02-17 17:28:47 -05:00
e := echo . New ( )
2023-02-23 04:24:04 -05:00
2023-03-21 18:37:51 -04:00
t := & Template {
2023-12-12 17:34:43 -05:00
templates : template . Must ( template . ParseFS ( templatesFS , "html/*.html" ) ) ,
2023-03-21 18:37:51 -04:00
}
e . Renderer = t
e . Pre ( middleware . RemoveTrailingSlash ( ) )
2023-07-04 22:17:04 -04:00
groupStatic := e . Group ( "/public/*" )
groupStatic . Use ( middleware . StaticWithConfig ( middleware . StaticConfig {
Root : "/" ,
Filesystem : http . FS ( publicFS ) ,
//TODO
//Browse: true,
} ) )
2023-04-21 21:34:18 -04:00
groupV1 := e . Group ( "/v1" )
groupV1 . Use ( middleware . AddTrailingSlash ( ) )
2023-07-04 16:05:23 -04:00
if cfg . Server . Api . Auth {
if len ( cfg . Server . Api . Key ) < 10 {
2023-04-21 21:34:18 -04:00
log . Fatal ( "server.api.auth is enabled, but server.api.key is too small (needs at least 10 characters)" )
}
groupV1 . Use ( middleware . KeyAuth ( func ( key string , c echo . Context ) ( bool , error ) {
2023-07-04 16:05:23 -04:00
return subtle . ConstantTimeCompare ( [ ] byte ( key ) , [ ] byte ( cfg . Server . Api . Key ) ) == 1 , nil
2023-04-21 21:34:18 -04:00
} ) )
2023-04-26 19:15:22 -04:00
log . Println ( "Key auth for /v1 activated" )
}
groupAdmin := e . Group ( "/admin" )
groupAdmin . Use ( middleware . AddTrailingSlash ( ) )
2023-07-04 16:05:23 -04:00
if cfg . Server . Admin . Auth {
if len ( cfg . Server . Admin . Username ) < 5 {
2023-04-26 19:15:22 -04:00
log . Fatal ( "server.admin.auth is enabled, but server.admin.username is too small (needs at least 5 characters)" )
}
2023-07-04 16:05:23 -04:00
if len ( cfg . Server . Admin . Password ) < 10 {
2023-04-26 19:15:22 -04:00
log . Fatal ( "server.admin.auth is enabled, but server.admin.password is too small (needs at least 10 characters)" )
}
groupAdmin . Use ( middleware . BasicAuth ( func ( username_entered , password_entered string , c echo . Context ) ( bool , error ) {
// Be careful to use constant time comparison to prevent timing attacks
2023-07-04 16:05:23 -04:00
if subtle . ConstantTimeCompare ( [ ] byte ( username_entered ) , [ ] byte ( cfg . Server . Admin . Username ) ) == 1 &&
subtle . ConstantTimeCompare ( [ ] byte ( password_entered ) , [ ] byte ( cfg . Server . Admin . Password ) ) == 1 {
2023-04-26 19:15:22 -04:00
return true , nil
}
return false , nil
} ) )
log . Println ( "Basic auth for /admin activated" )
2023-04-21 21:34:18 -04:00
}
2023-03-21 18:48:23 -04:00
// API Routes
2023-08-30 13:45:07 -04:00
mediaClient , err := media . NewMediaClientFromViper ( )
if err != nil {
log . Fatal ( "Error during NewMediaClientFromViper for API handlers" )
}
2023-11-20 15:13:42 -05:00
p := pave . New ( )
2023-11-20 14:14:00 -05:00
v1Handler := apihandler . V1Handler {
2023-08-30 13:45:07 -04:00
Config : cfg ,
MediaClient : mediaClient ,
2023-11-20 15:13:42 -05:00
Pave : & p ,
2023-08-30 13:45:07 -04:00
}
2023-03-21 18:48:23 -04:00
2023-11-20 15:40:52 -05:00
groupV1 . GET ( "" , v1Handler . V1GET )
2023-04-21 21:34:18 -04:00
2023-11-20 15:13:42 -05:00
if err := pave . EchoRegister [
apirequest . V1SeedPOST ,
2023-11-20 15:53:00 -05:00
apiresponse . V1SeedPOST ] ( groupV1 , & p , "/v1" , http . MethodPost , "/seed" , "Créer buckets manquants définis dans `server.documents.buckets`" , "V1SeedPOST" , v1Handler . V1SeedPOST ) ; err != nil {
2023-11-20 15:13:42 -05:00
log . Fatal ( err )
}
2024-01-23 16:50:03 -05:00
if err := pave . EchoRegister [
apirequest . V1DocumentKeyPUT ,
apiresponse . V1DocumentKeyPUT ] ( groupV1 , & p , "/v1" , http . MethodPut , "/:bucket/:document" , "Renommer un document" , "V1DocumentKeyPUT" , v1Handler . V1DocumentKeyPUT ) ; err != nil {
log . Fatal ( err )
}
2023-11-20 15:13:42 -05:00
if err := pave . EchoRegister [
apirequest . V1SpecGET ,
2023-11-20 15:28:37 -05:00
apiresponse . V1SpecGET ] ( groupV1 , & p , "/v1" , http . MethodGet , "/spec" , apihandler . DescriptionV1SpecGET , "V1SpecGET" , v1Handler . V1SpecGET ) ; err != nil {
2023-11-20 15:13:42 -05:00
log . Fatal ( err )
}
2023-03-21 18:48:23 -04:00
2023-11-20 16:14:22 -05:00
if err := pave . EchoRegister [
apirequest . V1BucketsGET ,
apiresponse . V1BucketsGET ] ( groupV1 , & p , "/v1" , http . MethodGet , "/bucket" , "List buckets" , "V1BucketsGET" , v1Handler . V1BucketsGET ) ; err != nil {
log . Fatal ( err )
}
2023-04-21 21:34:18 -04:00
2023-11-20 16:14:22 -05:00
if err := pave . EchoRegister [
apirequest . V1BucketGET ,
apiresponse . V1BucketGET ] ( groupV1 , & p , "/v1" , http . MethodGet , "/bucket/:bucket" , "Read bucket content" , "V1BucketGET" , v1Handler . V1BucketGET ) ; err != nil {
log . Fatal ( err )
}
2023-04-21 16:41:50 -04:00
2023-12-18 17:46:31 -05:00
if err := pave . EchoRegister [
apirequest . V1DocumentsPOST ,
apiresponse . V1DocumentsPOST ] ( groupV1 , & p , "/v1" , http . MethodPost , "/bucket/:bucket/many" , "Upload documents to specified bucket" , "V1DocumentsPOST" , v1Handler . V1DocumentsPOST ) ; err != nil {
log . Fatal ( err )
}
2023-11-20 16:34:19 -05:00
if err := pave . EchoRegister [
apirequest . V1DocumentPOST ,
apiresponse . V1DocumentPOST ] ( groupV1 , & p , "/v1" , http . MethodPost , "/bucket/:bucket" , "Upload document to specified bucket" , "V1DocumentPOST" , v1Handler . V1DocumentPOST ) ; err != nil {
log . Fatal ( err )
}
2023-04-24 17:19:52 -04:00
2023-11-20 15:40:52 -05:00
groupV1 . GET ( "/bucket/:bucket/:document" , v1Handler . V1DocumentGET )
2023-04-24 17:19:52 -04:00
2023-11-20 16:56:44 -05:00
if err := pave . EchoRegister [
apirequest . V1DocumentDELETE ,
2023-11-20 17:01:00 -05:00
apiresponse . V1DocumentDELETE ] ( groupV1 , & p , "/v1" , http . MethodDelete , "/bucket/:bucket/:document" , "Delete document in specified bucket" , "V1DocumentDELETE" , v1Handler . V1DocumentDELETE ) ; err != nil {
2023-11-20 16:56:44 -05:00
log . Fatal ( err )
}
2023-04-24 17:19:52 -04:00
2023-03-21 18:48:23 -04:00
// HTML Routes
2023-10-24 17:00:49 -04:00
client := http . DefaultClient
defer client . CloseIdleConnections ( )
apiClient , err := api . NewFromViper ( client )
2023-08-30 15:24:37 -04:00
if err != nil {
2023-10-24 17:00:49 -04:00
log . Fatal ( err )
2023-08-30 15:24:37 -04:00
}
2023-11-20 14:19:59 -05:00
webHandler := webhandler . WebHandler {
2023-08-30 15:24:37 -04:00
ApiClient : apiClient ,
}
2023-03-21 18:48:23 -04:00
2023-11-20 14:19:59 -05:00
e . GET ( "/" , webhandler . HandleIndex )
2023-03-21 18:37:51 -04:00
2023-11-20 14:19:59 -05:00
//e.GET("/a-propos", webhandler.HandleAPropos)
2023-03-21 20:29:06 -04:00
2023-11-20 14:19:59 -05:00
//e.GET("/actualite", webhandler.HandleActualite)
2023-03-21 20:29:06 -04:00
2023-11-20 14:19:59 -05:00
//e.GET("/actualite/:article", webhandler.HandleActualiteArticle)
2023-03-21 20:29:06 -04:00
2023-11-20 14:19:59 -05:00
e . GET ( "/vie-etudiante" , webhandler . HandleVieEtudiante )
2023-03-21 20:29:06 -04:00
2023-11-20 14:19:59 -05:00
e . GET ( "/vie-etudiante/:organisme" , webhandler . HandleVieEtudianteOrganisme )
2023-03-21 20:29:06 -04:00
2023-08-30 15:24:37 -04:00
e . GET ( "/documentation" , webHandler . HandleDocumentation )
2023-03-21 20:29:06 -04:00
2023-11-20 14:19:59 -05:00
e . GET ( "/formulaires" , webhandler . HandleFormulaires )
2023-03-21 20:29:06 -04:00
2023-04-26 18:27:58 -04:00
// Public Routes
2023-08-30 15:24:37 -04:00
e . GET ( "/public/documentation/:bucket/:document" , webHandler . HandlePublicDocumentation )
2023-04-26 18:27:58 -04:00
2023-04-26 19:15:22 -04:00
// Admin Routes
2023-11-20 14:19:59 -05:00
groupAdmin . GET ( "" , webhandler . HandleAdmin )
2023-04-26 19:15:22 -04:00
2023-08-30 15:24:37 -04:00
groupAdmin . GET ( "/documents/upload" , webHandler . HandleAdminDocumentsUpload )
2023-04-26 19:28:20 -04:00
2023-08-30 15:24:37 -04:00
groupAdmin . POST ( "/documents/upload" , webHandler . HandleAdminDocumentsUploadPOST )
2023-04-26 19:43:43 -04:00
2023-04-21 16:41:50 -04:00
e . Logger . Fatal ( e . Start (
2023-07-04 16:05:23 -04:00
fmt . Sprintf ( ":%d" , cfg . Server . Port ) ) )
2023-02-17 17:28:47 -05:00
}
2023-03-21 18:37:51 -04:00
func ( t * Template ) Render ( w io . Writer , name string , data interface { } , c echo . Context ) error {
return t . templates . ExecuteTemplate ( w , name , data )
}