commit eebe6971ecd7787990311886852814f2bf477144 Author: mahonec Date: Mon Jul 13 21:47:19 2020 +0000 first commit diff --git a/api.go b/api.go new file mode 100644 index 0000000..481f3ec --- /dev/null +++ b/api.go @@ -0,0 +1,114 @@ +package main + +import ( + "net/http" + "github.com/gorilla/mux" + "fmt" + "encoding/json" +) + +type SpeciesResp struct { + Name string + FirstFound int + LastFound int + Population int + Examples []string + Hashes []string +} + +// Returns first and last found of species, a list of "example soups" for the user to see in browser and the complete list of soups recorded in the db +func GetSpecies(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + species := vars["species"] + genus := vars["genus"] + family := vars["family"] + + // Start collecting data from sql db + apgcode := family + genus + "_" + species + life, err := SQLGetLife(apgcode) + + if err != nil { + panic(err) + } + + samples, err := SQLGetSamples(apgcode) + + if err != nil { + panic(err) + } + + // Examples should be the first found and the lastfound, + // if the first found is the last found than skip it + var examples []string + if len(samples) == 1 { + examples = append(examples, samples[0].Hash) + } else { + examples = append(examples, samples[0].Hash) + examples = append(examples, samples[len(samples) - 1].Hash) + } + + // Get list of all found hashes + hashes := make([]string, len(samples)) + for i, s := range samples { + hashes[i] = s.Hash + } + + ret := SpeciesResp{ + Name: life.Apgcode, + FirstFound: life.FirstFound, + LastFound: life.LastFound, + Population: life.Population, + Examples: examples, + Hashes: hashes, + } + + b, err := json.Marshal(ret) + if err != nil { + panic(err) + } + fmt.Fprintf(w, "%s", string(b)) +} + +// Returns list of species in a genus ordered by population +func GetGenus(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + genus := vars["genus"] + family := vars["family"] + + apgcode := family + genus + ret, err := SQLGetGenus(apgcode) + if err != nil { + panic(err) + } + + // No processing is needed here so we just send the json + + b, err := json.Marshal(ret) + if err != nil { + panic(err) + } + fmt.Fprintf(w, "%s", string(b)) +} + +func GetFamily(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + family := vars["family"] + + ret, err := SQLGetFamily(family) + if err != nil { + panic(err) + } + + // No processing is needed here so we just send the json + + b, err := json.Marshal(ret) + if err != nil { + panic(err) + } + fmt.Fprintf(w, "%s", string(b)) +} + + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7ed90a9 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module soups + +go 1.13 + +require ( + github.com/go-sql-driver/mysql v1.5.0 + github.com/gorilla/mux v1.7.4 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2673b6a --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/main.go b/main.go new file mode 100644 index 0000000..49c6ea0 --- /dev/null +++ b/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "net/http" + "github.com/gorilla/mux" +) + +func main() { + // Create mux handler + r := mux.NewRouter() + + r.HandleFunc("/census/{family}/{genus}/{species}", GetSpecies) + r.HandleFunc("/census/{family}/{genus}", GetGenus) + r.HandleFunc("/census/{family}", GetFamily) + + //r.HandleFunc("/census", Cenus) + + http.ListenAndServe(":8080", r) +} + diff --git a/soups b/soups new file mode 100755 index 0000000..4bc50d8 Binary files /dev/null and b/soups differ diff --git a/sql.go b/sql.go new file mode 100644 index 0000000..35de429 --- /dev/null +++ b/sql.go @@ -0,0 +1,183 @@ +package main + +import ( + "database/sql" + _ "github.com/go-sql-driver/mysql" +) + +type Life struct { + Apgcode string + Population int + FirstFound int + LastFound int +} + +type Sample struct { + Apgcode string + Id int + Hash string +} + +// Connect to database +func dbConnect() (db *sql.DB) { + if err != nil { + panic(err) + } + + return db +} + +// Get Life record from census +func SQLGetLife(code string) (*Life, error) { + db := dbConnect() + defer db.Close() + + var apgcode string + var population, firstFound, lastFound int + row := db.QueryRow("SELECT * FROM census WHERE apgcode=?", code) + err := row.Scan(&apgcode, &population, &firstFound, &lastFound) + + if err != nil { + return nil, err + } + + var life = Life { + apgcode, + population, + firstFound, + lastFound, + } + + return &life, nil +} + +// Get array of Sample records order by id +func SQLGetSamples(code string) ([]Sample, error) { + db := dbConnect() + defer db.Close() + + rows, err := db.Query("SELECT * FROM samples WHERE apgcode=? order by id", code) + defer rows.Close() + + if err != nil { + return nil, err + } + + var samples []Sample + var sample = Sample{} + + // Iterate over rows + for rows.Next() { + err = rows.Scan(&sample.Apgcode, &sample.Id, &sample.Hash) + if err != nil { + return nil, err + } + samples = append(samples, sample) + } + + return samples, nil +} + +type Genus struct { + Name string + Population int + Species []Life +} + +// Get array of lifes of a given genus +func SQLGetGenus(code string) (*Genus, error) { + db := dbConnect() + defer db.Close() + + rows, err := db.Query("SELECT * FROM census WHERE apgcode LIKE ? ORDER BY population DESC, firstFound", code + "%") + defer rows.Close() + if err != nil { + return nil, err + } + + var genus = Genus { + Name: code, + } + + // Iterate over rows + for rows.Next() { + var life = Life{} + + err = rows.Scan(&life.Apgcode, &life.Population, &life.FirstFound, &life.LastFound) + if err != nil { + panic(err) + } + + genus.Species = append(genus.Species, life) + } + + // Get population of genus + genus.Population = getPopulationOfCode(code + "%") + + return &genus, nil +} + +type GenusProxy struct { + Name string + Count int +} + +type Family struct { + Name string + Genera []GenusProxy +} + +// Get array of genus in the family, each genus should have the count of unique species +func SQLGetFamily(code string) (*Family, error) { + db := dbConnect() + defer db.Close() + + // Returns list of genera and their count of species below them, ordered by genus and formatted nicely + // TLDR I'm a freaking god and here is my SQL to prove it + rows, err := db.Query("SELECT SUBSTRING_INDEX(apgcode, \"_\", 1) AS a, COUNT(apgcode) FROM census WHERE apgcode LIKE ? GROUP BY a ORDER BY SUBSTR(a, LENGTH(?)-1, LENGTH(a)-(LENGTH(?)-1))", code + "%", code, code) + defer rows.Close() + + if err != nil { + panic(err) + } + + var family = Family { + Name: code, + } + + for rows.Next() { + var proxy GenusProxy + rows.Scan(&proxy.Name, &proxy.Count) + family.Genera = append(family.Genera, proxy) + } + + return &family, nil +} + +// Totals the population of species matching code +func getPopulationOfCode(code string) (pop int) { + db := dbConnect() + defer db.Close() + + err := db.QueryRow("SELECT SUM(population) FROM census WHERE apgcode LIKE ?", code).Scan(&pop) + + if err != nil { + return 0 + } + + return pop +} + +// Get the count of unique species of matching code +func getCountOfCode(code string) (count int) { + db := dbConnect() + defer db.Close() + + err := db.QueryRow("SELECT COUNT(population) FROM census WHERE apgcode LIKE ?", code).Scan(&count) + + if err != nil { + return 0 + } + + return count +}