package main import ( "database/sql" "encoding/json" "fmt" "log" "net/http" "os" _ "github.com/lib/pq" ) func WriteJson(w http.ResponseWriter, v any) { w.Header().Set("Content-Type", "application/json") jsonData, err := json.Marshal(v) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write(jsonData) } func handleVersion(w http.ResponseWriter, r *http.Request) { version := struct { Version string `json:version` }{ Version: "0.1.0-demo", } WriteJson(w, version) } func InitDatabase(db *sql.DB) { _, err := db.Exec( `CREATE TABLE IF NOT EXISTS ingredient ( id SERIAL, name VARCHAR(64) UNIQUE NOT NULL, icon VARCHAR(64), price INT, PRIMARY KEY(id) )`) if err != nil { log.Fatal(err) } log.Print("created table for ingredients") _, err = db.Exec( `CREATE TABLE IF NOT EXISTS recipe ( id SERIAL, name VARCHAR(64) UNIQUE NOT NULL, icon VARCHAR(64), PRIMARY KEY(id) )`) if err != nil { log.Fatal(err) } log.Print("created table for receipts") _, err = db.Exec( `CREATE TABLE IF NOT EXISTS recipe_ingredient ( recipe_id INT NOT NULL, ingredient_d INT NOT NULL, amount INT, FOREIGN KEY (recipe_id) REFERENCES recipe(id), FOREIGN KEY (ingredient_d) REFERENCES ingredient(id) )`) if err != nil { log.Fatal(err) } log.Print("created table for recipe ingredients") } type Ingredient struct { Name string `json:name` Icon string `json:icon` Price int `json:price` } type Recipe struct { Name string `json:name` Icon string `json:icon` } type RecipeIngredient struct { Recipe int `json:recipe` Ingredient int `json:ingredient` Amount int `json:amount` } func GetRecipes(db *sql.DB) ([]Recipe, error) { rows, err := db.Query("SELECT name, icon FROM recipe") if err != nil { return nil, err } recipes := []Recipe{} for rows.Next() { recipe := Recipe{} rows.Scan(&recipe.Name, &recipe.Icon) recipes = append(recipes, recipe) } return recipes, nil } func GetIngredients(db *sql.DB) ([]Ingredient, error) { rows, err := db.Query("SELECT name, icon, price FROM ingredient") if err != nil { return nil, err } ingredients := []Ingredient{} for rows.Next() { ingredient := Ingredient{} rows.Scan(&ingredient.Name, &ingredient.Icon, &ingredient.Price) ingredients = append(ingredients, ingredient) } return ingredients, nil } func GetRecipeIngredients(recipe_name string, db *sql.DB) ([]Ingredient, error) { rows, err := db.Query(fmt.Sprintf("SELECT id FROM recipe WHERE name='%s'", recipe_name)) if err != nil { return nil, err } id := 0 if rows.Next() { rows.Scan(&id) } else { return nil, nil } rows, err = db.Query(fmt.Sprintf("SELECT ingredient.name, ingredient.icon, ingredient.price FROM ingredient INNER JOIN recipe_ingredient ON recipe_ingredient.ingredient_d=ingredient.id WHERE recipe_ingredient.recipe_id=%d", id)) if err != nil { return nil, err } ingredients := []Ingredient{} for rows.Next() { ingredient := Ingredient{} rows.Scan(&ingredient.Name, &ingredient.Icon, &ingredient.Price) ingredients = append(ingredients, ingredient) } return ingredients, nil } func HandleApiRecipes(w http.ResponseWriter, r *http.Request, db *sql.DB) { if r.Method != http.MethodGet && r.Method != "" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) log.Print("Mismatched method: ", r.Method) return } recipes, err := GetRecipes(db) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) log.Print("Error fetching recipes: ", err) return } WriteJson(w, recipes) log.Print("Fetched ", len(recipes), " recipes from database") } func HandleApiIngredients(w http.ResponseWriter, r *http.Request, db *sql.DB) { if r.Method != http.MethodGet && r.Method != "" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) log.Print("Mismatched method: ", r.Method) return } ingredients, err := GetIngredients(db) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) log.Print("Error fetching ingredients: ", err) return } log.Print("Fetched ", len(ingredients), " ingredients from database") WriteJson(w, ingredients) } func HandleApiRecipeIngredients(w http.ResponseWriter, r *http.Request, db *sql.DB) { if r.Method != http.MethodGet && r.Method != "" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) log.Print("Mismatched method: ", r.Method) return } recipe_name := r.PathValue("name") log.Print("Fetching ingredients for recipe ", recipe_name) ingredients, err := GetRecipeIngredients(recipe_name, db) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) log.Print("Error fetching ingredients: ", err) return } if ingredients == nil { http.NotFound(w, r) return } WriteJson(w, ingredients) } func HandleApiIngedientAdd(w http.ResponseWriter, r *http.Request, db *sql.DB) { if r.Method != http.MethodGet && r.Method != "" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) log.Print("Mismatched method: ", r.Method) return } } func main() { dbconn := os.Getenv("GREPFOOD_DATABASE_CONNECTION") db, err := sql.Open("postgres", dbconn) if err != nil { log.Fatal(err) } log.Print("connected to database: ", dbconn) InitDatabase(db) http.HandleFunc("/version", handleVersion) http.Handle("/", http.FileServer(http.Dir("static"))) http.HandleFunc("/api/recipes", func(w http.ResponseWriter, r *http.Request) { HandleApiRecipes(w, r, db) }) http.HandleFunc("/api/ingredients", func(w http.ResponseWriter, r *http.Request) { HandleApiIngredients(w, r, db) }) http.HandleFunc("/api/recipe/{name}/ingredients", func(w http.ResponseWriter, r *http.Request) { HandleApiRecipeIngredients(w, r, db) }) log.Fatal(http.ListenAndServe(os.Getenv("GREPFOOD_SYNCSERVER_PORT"), nil)) }