2024-10-15 06:17:28 +00:00
package main
import (
2024-10-15 21:37:26 +00:00
"database/sql"
"encoding/json"
"fmt"
2024-10-15 06:17:28 +00:00
"log"
"net/http"
"os"
2024-10-28 15:29:59 +00:00
"path/filepath"
2024-10-15 10:22:05 +00:00
_ "github.com/lib/pq"
2024-10-15 06:17:28 +00:00
)
func WriteJson ( w http . ResponseWriter , v any ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
jsonData , err := json . Marshal ( v )
if err != nil {
2024-10-15 21:37:26 +00:00
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
2024-10-15 06:17:28 +00:00
}
w . Write ( jsonData )
}
func handleVersion ( w http . ResponseWriter , r * http . Request ) {
2024-10-15 21:37:26 +00:00
version := struct {
Version string ` json:version `
} {
2024-10-15 06:17:28 +00:00
Version : "0.1.0-demo" ,
}
WriteJson ( w , version )
}
2024-10-15 10:22:05 +00:00
func InitDatabase ( db * sql . DB ) {
_ , err := db . Exec (
2024-10-15 21:37:26 +00:00
` CREATE TABLE IF NOT EXISTS ingredient (
2024-10-15 10:22:05 +00:00
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 (
2024-10-15 21:37:26 +00:00
` CREATE TABLE IF NOT EXISTS recipe (
2024-10-15 10:22:05 +00:00
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 (
2024-10-15 21:37:26 +00:00
` CREATE TABLE IF NOT EXISTS recipe_ingredient (
2024-10-15 10:22:05 +00:00
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 {
2024-10-15 21:37:26 +00:00
Name string ` json:name `
Icon string ` json:icon `
Price int ` json:price `
2024-10-15 10:22:05 +00:00
}
type Recipe struct {
Name string ` json:name `
Icon string ` json:icon `
}
type RecipeIngredient struct {
2024-10-15 21:37:26 +00:00
Recipe int ` json:recipe `
2024-10-15 10:22:05 +00:00
Ingredient int ` json:ingredient `
2024-10-15 21:37:26 +00:00
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
}
2024-10-15 10:22:05 +00:00
}
2024-10-28 15:29:59 +00:00
func HandleGetImage ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodGet && r . Method != "" {
http . Error ( w , "Method not allowed" , http . StatusMethodNotAllowed )
log . Print ( "Mismatched method: " , r . Method )
return
}
var imageSearchPath = os . Getenv ( "GREPFOOD_IMAGE_LOCAL_STORAGE_PATH" ) ;
bytes , err := os . ReadFile ( filepath . Join ( imageSearchPath , r . PathValue ( "name" ) ) )
if err != nil {
log . Print ( err )
http . Error ( w , "File not found" , http . StatusNotFound )
}
w . Header ( ) . Set ( "Content-Type" , "image/png" )
w . Write ( bytes )
}
2024-10-15 06:17:28 +00:00
func main ( ) {
2024-10-15 10:22:05 +00:00
dbconn := os . Getenv ( "GREPFOOD_DATABASE_CONNECTION" )
db , err := sql . Open ( "postgres" , dbconn )
if err != nil {
2024-10-15 21:37:26 +00:00
log . Fatal ( err )
2024-10-15 10:22:05 +00:00
}
log . Print ( "connected to database: " , dbconn )
InitDatabase ( db )
2024-10-15 06:17:28 +00:00
http . HandleFunc ( "/version" , handleVersion )
2024-10-15 21:37:26 +00:00
http . Handle ( "/" , http . FileServer ( http . Dir ( "static" ) ) )
2024-10-15 10:22:05 +00:00
http . HandleFunc ( "/api/recipes" , func ( w http . ResponseWriter , r * http . Request ) {
2024-10-15 21:37:26 +00:00
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 )
2024-10-15 10:22:05 +00:00
} )
2024-10-28 15:29:59 +00:00
http . HandleFunc ( "/api/image/{name}" , func ( w http . ResponseWriter , r * http . Request ) {
HandleGetImage ( w , r )
} )
2024-10-15 06:17:28 +00:00
log . Fatal ( http . ListenAndServe ( os . Getenv ( "GREPFOOD_SYNCSERVER_PORT" ) , nil ) )
}