Import project backup into new git repo
This commit is contained in:
commit
4ac3625f45
15 changed files with 1340 additions and 0 deletions
108
cmd/root.go
Normal file
108
cmd/root.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"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-agenda",
|
||||
Short: "Application de distribution d'agendas",
|
||||
// Uncomment the following line if your bare application
|
||||
// has an action associated with it:
|
||||
// Run: func(cmd *cobra.Command, args []string) { },
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
// Cobra supports persistent flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin-agenda.yaml)")
|
||||
|
||||
// bottin.host
|
||||
rootCmd.PersistentFlags().String(
|
||||
"bottin-host", "",
|
||||
"The host of the bottin instance (config: 'bottin.host')")
|
||||
viper.BindPFlag(
|
||||
"bottin.host",
|
||||
rootCmd.PersistentFlags().Lookup("bottin-host"))
|
||||
rootCmd.MarkPersistentFlagRequired("bottin.host")
|
||||
|
||||
// bottin.protocol
|
||||
rootCmd.PersistentFlags().String(
|
||||
"bottin-protocol", "http",
|
||||
"The protocol of the bottin instance (config: 'bottin.protocol')")
|
||||
viper.BindPFlag(
|
||||
"bottin.protocol",
|
||||
rootCmd.PersistentFlags().Lookup("bottin-protocol"))
|
||||
rootCmd.MarkPersistentFlagRequired("bottin.protocol")
|
||||
|
||||
// bottin.port
|
||||
rootCmd.PersistentFlags().Int(
|
||||
"bottin-port", 1312,
|
||||
"The port to the bottin instance (config: 'bottin.port')")
|
||||
viper.BindPFlag(
|
||||
"bottin.port",
|
||||
rootCmd.PersistentFlags().Lookup("bottin-port"))
|
||||
rootCmd.MarkPersistentFlagRequired("bottin.port")
|
||||
|
||||
// bottin.username
|
||||
rootCmd.PersistentFlags().String(
|
||||
"bottin-username", "",
|
||||
"The username to the bottin instance (config: 'bottin.username')")
|
||||
viper.BindPFlag(
|
||||
"bottin.username",
|
||||
rootCmd.PersistentFlags().Lookup("bottin-username"))
|
||||
rootCmd.MarkPersistentFlagRequired("bottin.username")
|
||||
|
||||
// bottin.password
|
||||
rootCmd.PersistentFlags().String(
|
||||
"bottin-password", "",
|
||||
"The password to the bottin instance (config: 'bottin.password')")
|
||||
viper.BindPFlag(
|
||||
"bottin.password",
|
||||
rootCmd.PersistentFlags().Lookup("bottin-password"))
|
||||
rootCmd.MarkPersistentFlagRequired("bottin.password")
|
||||
}
|
||||
|
||||
// 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-agenda" (without extension).
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigType("yaml")
|
||||
viper.SetConfigName(".bottin-agenda")
|
||||
}
|
||||
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
log.Printf("Using config file: %s\n", viper.ConfigFileUsed())
|
||||
}
|
||||
}
|
131
cmd/scan.go
Normal file
131
cmd/scan.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.agecem.com/agecem/bottin-agenda/data"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
protocol, host, username, password string
|
||||
port int
|
||||
check bool
|
||||
)
|
||||
|
||||
// scanCmd represents the scan command
|
||||
var scanCmd = &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Check a bottin instance for existing membre",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
args_length := len(args)
|
||||
|
||||
if args_length != 1 {
|
||||
log.Fatalf("[e] Wrong number of arguments, got '%d' needs 1", args_length)
|
||||
} else {
|
||||
|
||||
// Search locally for corresponding Membre
|
||||
membre, err := readMembre(args[0])
|
||||
if err != nil {
|
||||
log.Printf("[e] %s", err)
|
||||
}
|
||||
|
||||
membre_exists := (membre.ID != 0)
|
||||
|
||||
if membre_exists {
|
||||
log.Fatal("[e] Membre is already in the bottin-agenda database")
|
||||
} else {
|
||||
log.Println("[i] Membre not already in bottin-agenda, confirming existence with bottin")
|
||||
|
||||
// Search in bottin for corresponding Membre
|
||||
membre_bottin, err := readMembreBottin(args[0])
|
||||
if err != nil {
|
||||
log.Fatalf("[e] %s", err)
|
||||
}
|
||||
|
||||
membre_bottin_exists := (membre_bottin.ID != 0)
|
||||
|
||||
if membre_bottin_exists {
|
||||
|
||||
log.Println("[ok] Membre found in bottin")
|
||||
|
||||
if !check {
|
||||
log.Println("[i] Adding to bottin-agenda")
|
||||
|
||||
membre_bottin.CreatedAt = time.Now()
|
||||
membre_bottin.UpdatedAt = time.Now()
|
||||
|
||||
// Add to bottin-agenda database
|
||||
data.InsertMembre(&membre_bottin)
|
||||
} else {
|
||||
log.Fatal("[i] Skipping insert because of --check flag")
|
||||
}
|
||||
} else {
|
||||
log.Fatal("[e] Membre does not exist in bottin database")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(scanCmd)
|
||||
|
||||
// check
|
||||
scanCmd.PersistentFlags().BoolVar(
|
||||
&check, "check", false,
|
||||
"Do not write anything to database.")
|
||||
}
|
||||
|
||||
func updateFlagsBottin() {
|
||||
protocol = viper.GetString("bottin.protocol")
|
||||
host = viper.GetString("bottin.host")
|
||||
port = viper.GetInt("bottin.port")
|
||||
username = viper.GetString("bottin.username")
|
||||
password = viper.GetString("bottin.password")
|
||||
}
|
||||
|
||||
func readMembre(num_etud string) (data.Membre, error) {
|
||||
data.OpenDatabase()
|
||||
data.MigrateDatabase()
|
||||
|
||||
membre, err := data.ReadMembre(num_etud)
|
||||
if err != nil {
|
||||
return membre, err
|
||||
}
|
||||
|
||||
return membre, nil
|
||||
}
|
||||
|
||||
func readMembreBottin(num_etud string) (data.Membre, error) {
|
||||
updateFlagsBottin()
|
||||
|
||||
membre := data.Membre{}
|
||||
|
||||
request := fmt.Sprintf("%s://%s:%s@%s:%d/v1/membre/%s", protocol, username, password, host, port, num_etud)
|
||||
|
||||
response, err := http.Get(request)
|
||||
if err != nil {
|
||||
return membre, err
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return membre, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(body), &membre)
|
||||
if err != nil {
|
||||
return membre, err
|
||||
}
|
||||
|
||||
return membre, nil
|
||||
}
|
259
cmd/server.go
Normal file
259
cmd/server.go
Normal file
|
@ -0,0 +1,259 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.agecem.com/agecem/bottin-agenda/data"
|
||||
"git.agecem.com/agecem/bottin-agenda/embed"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
html string
|
||||
)
|
||||
|
||||
// serverCmd represents the server command
|
||||
var serverCmd = &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "Run the bottin-agenda server",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
data.OpenDatabase()
|
||||
data.MigrateDatabase()
|
||||
|
||||
runServer()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
declareFlags()
|
||||
rootCmd.AddCommand(serverCmd)
|
||||
|
||||
html = embed.ReadHtml()
|
||||
}
|
||||
|
||||
func declareFlags() {
|
||||
// db.type
|
||||
serverCmd.PersistentFlags().String(
|
||||
"db-type", "",
|
||||
"Database type (config: 'db.type')")
|
||||
viper.BindPFlag(
|
||||
"db.type",
|
||||
serverCmd.PersistentFlags().Lookup("db-type"))
|
||||
serverCmd.MarkPersistentFlagRequired("db.type")
|
||||
|
||||
// db.sqlite.path
|
||||
serverCmd.PersistentFlags().String(
|
||||
"db-sqlite-path", "",
|
||||
"Path to sqlite database (config: 'db.sqlite.path')")
|
||||
viper.BindPFlag(
|
||||
"db.sqlite.path",
|
||||
serverCmd.PersistentFlags().Lookup("db-sqlite-path"))
|
||||
|
||||
// server.port
|
||||
serverCmd.PersistentFlags().Int(
|
||||
"server-port", 1313,
|
||||
"The port on which the web application will server content (config: 'server.port')")
|
||||
viper.BindPFlag(
|
||||
"server.port",
|
||||
serverCmd.PersistentFlags().Lookup("server-port"))
|
||||
|
||||
// server.static_dir
|
||||
serverCmd.PersistentFlags().String(
|
||||
"static-dir", "/var/lib/bottin-agenda/static",
|
||||
"DEPRECATED The directory containing static assets (config: 'server.static_dir')")
|
||||
viper.BindPFlag(
|
||||
"server.static_dir",
|
||||
serverCmd.PersistentFlags().Lookup("static-dir"))
|
||||
|
||||
// login.username
|
||||
serverCmd.PersistentFlags().String(
|
||||
"login-username", "agenda",
|
||||
"The username to login to the web ui. (config: 'login.username')")
|
||||
viper.BindPFlag(
|
||||
"login.username",
|
||||
serverCmd.PersistentFlags().Lookup("login-username"))
|
||||
|
||||
// login.password
|
||||
serverCmd.PersistentFlags().String(
|
||||
"login-password", "agenda",
|
||||
"The password to login to the web ui. (config: 'login.password')")
|
||||
viper.BindPFlag(
|
||||
"login.password",
|
||||
serverCmd.PersistentFlags().Lookup("login-password"))
|
||||
|
||||
// import.insert-batch-size
|
||||
serverCmd.PersistentFlags().Int(
|
||||
"insert-batch-size", 500,
|
||||
"The amount of inserts to do per batch (config: 'import.insert_batch_size')")
|
||||
viper.BindPFlag(
|
||||
"import.insert_batch_size",
|
||||
serverCmd.PersistentFlags().Lookup("insert-batch-size"))
|
||||
}
|
||||
|
||||
func runServer() {
|
||||
port := fmt.Sprintf(":%d", viper.GetInt("server.port"))
|
||||
|
||||
e := echo.New()
|
||||
g := e.Group("")
|
||||
|
||||
e.Pre(middleware.RemoveTrailingSlash())
|
||||
g.Use(middleware.BasicAuth(basicAuther))
|
||||
|
||||
g.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||
Format: "${time_rfc3339_nano} method=${method}, uri=${uri}, status=${status}" + "\n",
|
||||
}))
|
||||
|
||||
// v1 routes
|
||||
g.GET("/v1", showAPISpec)
|
||||
g.GET("/v1/scan/:num_etud", showScan)
|
||||
g.POST("/v1/scan/:num_etud", postScan)
|
||||
|
||||
// html routes
|
||||
registerHTMLRoutes(g)
|
||||
|
||||
e.Logger.Fatal(e.Start(port))
|
||||
}
|
||||
|
||||
func basicAuther(username, password string, context echo.Context) (bool, error) {
|
||||
if subtle.ConstantTimeCompare([]byte(username), []byte(viper.GetString("login.username"))) == 1 &&
|
||||
subtle.ConstantTimeCompare([]byte(password), []byte(viper.GetString("login.password"))) == 1 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func showAPISpec(c echo.Context) error {
|
||||
return c.String(http.StatusOK, `agecem/bottin-agenda
|
||||
API Spec
|
||||
-----
|
||||
"/v1" | GET | Show API specifications
|
||||
"/v1/scan/:num_etud" | GET | Show JSON representing a membre with :num_etud
|
||||
"/v1/scan/:num_etud" | POST | Scan bottin for membre with :num_etud and add to local db if not already present
|
||||
-----
|
||||
`)
|
||||
}
|
||||
|
||||
// Handler for "-X GET /v1/scan/:num_etud"
|
||||
func showScan(c echo.Context) error {
|
||||
num_etud := c.Param("num_etud")
|
||||
membre, err := readMembre(num_etud)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusOK, map[string]error{
|
||||
"message": err,
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(200, membre)
|
||||
}
|
||||
|
||||
// Handler for "-X POST /v1/scan/:num_etud"
|
||||
func postScan(c echo.Context) error {
|
||||
num_etud := c.Param("num_etud")
|
||||
|
||||
membre, err := scanMembre(num_etud)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{
|
||||
"message": fmt.Sprintf("Erreur lors de l'insertion de Membre '%s'", num_etud),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]string{
|
||||
"message": fmt.Sprintf("Membre '%s' scannéE avec succès et peut reçevoir son agenda.", membre.NumEtud),
|
||||
})
|
||||
}
|
||||
|
||||
// Tries to insert a membre into the local db and return it with any error encountered
|
||||
func scanMembre(num_etud string) (data.Membre, error) {
|
||||
membre, err := readMembre(num_etud)
|
||||
if num_etud == "" {
|
||||
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return membre, err
|
||||
}
|
||||
membre_exists := (membre.ID != 0)
|
||||
|
||||
membre_bottin, err := readMembreBottin(num_etud)
|
||||
if err != nil {
|
||||
return membre, err
|
||||
}
|
||||
membre_bottin_exists := (membre_bottin.ID != 0)
|
||||
|
||||
if membre_exists {
|
||||
return membre, errors.New(fmt.Sprintf("Membre '%s' a déjà reçuE son agenda", num_etud))
|
||||
}
|
||||
|
||||
if !membre_bottin_exists {
|
||||
return membre, errors.New(fmt.Sprintf("Membre '%s' non-trouvéE dans le bottin. Est-ce bien entré?", num_etud))
|
||||
}
|
||||
|
||||
// This should happen if membre is not scanned and is present in bottin
|
||||
membre_bottin.CreatedAt = time.Now()
|
||||
membre_bottin.UpdatedAt = time.Now()
|
||||
err, _ = data.InsertMembre(&membre_bottin)
|
||||
if err != nil {
|
||||
return membre_bottin, err
|
||||
}
|
||||
|
||||
return membre_bottin, nil
|
||||
}
|
||||
|
||||
func registerHTMLRoutes(g *echo.Group) {
|
||||
g.GET("/", showScanHTML)
|
||||
g.POST("/", postScanHTML)
|
||||
}
|
||||
|
||||
func showScanHTML(c echo.Context) error {
|
||||
num_etud := c.QueryParam("num_etud")
|
||||
|
||||
if num_etud == "" {
|
||||
return c.HTML(http.StatusOK, html)
|
||||
}
|
||||
|
||||
html_filled := html
|
||||
|
||||
if num_etud != "" {
|
||||
html_filled = fmt.Sprintf(`%s
|
||||
Numéro étudiant: %s`, html, num_etud)
|
||||
}
|
||||
|
||||
return c.HTML(http.StatusOK, html_filled)
|
||||
}
|
||||
|
||||
func postScanHTML(c echo.Context) error {
|
||||
num_etud := c.FormValue("num_etud")
|
||||
|
||||
if num_etud == "" {
|
||||
return c.HTML(http.StatusOK, html)
|
||||
}
|
||||
|
||||
membre, err := scanMembre(num_etud)
|
||||
if err != nil {
|
||||
return c.HTML(http.StatusInternalServerError, fmt.Sprintf(`%s
|
||||
Erreur: %s`, html, err))
|
||||
}
|
||||
|
||||
html_filled := html
|
||||
|
||||
if num_etud != "" {
|
||||
if membre.ID == 0 {
|
||||
html_filled = fmt.Sprintf(`%s
|
||||
Erreur lors de l'insertion de membre %s. Veuillez SVP signaler cette erreur.`, html, num_etud)
|
||||
} else {
|
||||
|
||||
html_filled = fmt.Sprintf(`%s
|
||||
Membre '%s' scannéE avec succès et peut recevoir son agenda.`, html, num_etud)
|
||||
}
|
||||
}
|
||||
|
||||
return c.HTML(http.StatusOK, html_filled)
|
||||
}
|
Reference in a new issue