package grils import ( "fmt" "log" "net/http" "regexp" "strconv" "strings" "time" "fagott.pw/charakterin" "fagott.pw/grilist/cache" "fagott.pw/grilist/eventlogging" "fagott.pw/grilist/frontend" "fagott.pw/grilist/grilist" "fagott.pw/grilist/models" "fagott.pw/grilist/util" "github.com/julienschmidt/httprouter" ) var ( pgArrayReg = regexp.MustCompile(`(((?P(([^",\\{}\s(NULL)])+|"([^"\\]|\\"|\\\\)*")))(,)?)`) pgValueIdx int ) func findIdx() { for i, subexp := range pgArrayReg.SubexpNames() { if subexp == "value" { pgValueIdx = i break } } } type GrilsModule struct { g *grilist.Grilist c *cache.Cache } func (m *GrilsModule) Name() string { return "Grils" } func (m *GrilsModule) Init(g *grilist.Grilist) { findIdx() m.g = g m.g.Router.GET("/gril/:id", m.viewGril) m.g.Router.GET("/gril/:id/*rest", m.viewGril) m.c = cache.New() } func (m *GrilsModule) getGrils(whereClause string, params ...interface{}) ([]*models.Gril, error) { var grils []*models.Gril rows, err := m.g.DB.Query(fmt.Sprintf(`SELECT id, kanji_name, romaji_name, other_names, updated_at, age, birthday, tags FROM grilist.grils_flattened WHERE %s`, whereClause), params...) if err != nil { return nil, err } defer rows.Close() for rows.Next() { gril := &models.Gril{} var tags []byte var otherNames []byte var kanjiName *string err = rows.Scan(&gril.ID, &kanjiName, &gril.RomajiName, &otherNames, &gril.UpdatedAt, &gril.Age, &gril.Birthday, &tags) if err != nil { log.Println(gril.ID) log.Println("error scanning in getGrils:", err) continue } if kanjiName != nil { gril.KanjiName = *kanjiName } gril.Tags = pgArray(tags) gril.OtherNames = pgArray(otherNames) m.c.Insert(gril.ID, gril) grils = append(grils, gril) } return grils, nil } func (m *GrilsModule) GetListsOfGril(gril *models.Gril) error { rows, err := m.g.DB.Query(`SELECT list_id FROM grilist.lists_grils WHERE gril_id = $1`, gril.ID) if err != nil { return err } defer rows.Close() for rows.Next() { var listID int if err := rows.Scan(&listID); err != nil { log.Println(err) continue } gril.Lists = append(gril.Lists, listID) } return nil } func (m *GrilsModule) ProvideDashboardData(user *charakterin.User) []grilist.DashboardCategory { var categories []grilist.DashboardCategory t1 := time.Now() rows, err := m.g.DB.Query( `SELECT id, romaji_name, kanji_name FROM grilist.get_recently_updated_grils();`) if err != nil { log.Println(err) return categories } defer rows.Close() cat := grilist.DashboardCategory{ Title: "Neue Grils", } for rows.Next() { var g models.Gril if err := rows.Scan(&g.ID, &g.RomajiName, &g.KanjiName); err != nil { log.Println(err) continue } cat.Cards = append(cat.Cards, frontend.Card{ Title: g.RomajiName, Description: g.KanjiName, Size: "medium", Actions: []frontend.Action{ frontend.Action{ Name: "anguckieren", Link: "/gril/" + g.Slug(), }, }, }) } categories = append(categories, cat) log.Printf("get_newest_grils_overall: %dms", time.Since(t1).Nanoseconds()/1000000) return categories } func (m *GrilsModule) FromID(id int) (*models.Gril, error) { if g, ok := m.c.Get(id); ok { return g.(*models.Gril), nil } gril := &models.Gril{ ID: id, } t1 := time.Now() err := m.g.DB.QueryRow(`SELECT updated_at, age, birthday FROM grilist.grils WHERE id = $1`, id).Scan(&gril.UpdatedAt, &gril.Age, &gril.Birthday) log.Printf("get_gril_from_id_raw: %dms", time.Since(t1).Nanoseconds()/1000000) if err != nil { return nil, err } // Namen rausholen rows, err := m.g.DB.Query(`SELECT name, name_type FROM grilist.gril_names WHERE gril_id = $1`, id) if err != nil { return nil, err } log.Printf("get_gril_from_id_names: %dms", time.Since(t1).Nanoseconds()/1000000) defer rows.Close() for rows.Next() { var name *string var name_type int if err := rows.Scan(&name, &name_type); err != nil { return nil, err } switch name_type { case 0: gril.KanjiName = *name break case 1: gril.RomajiName = *name default: gril.OtherNames = append(gril.OtherNames, *name) } } m.c.Insert(id, gril) log.Printf("get_gril_from_id: %dms", time.Since(t1).Nanoseconds()/1000000) return gril, nil } func (m *GrilsModule) FromIDs(ids []int) ([]*models.Gril, error) { if len(ids) == 0 { return make([]*models.Gril, 0), nil } t1 := time.Now() idList := "(" first := true var list []*models.Gril for _, v := range ids { if m.c.Has(v) { g, _ := m.c.Get(v) list = append(list, g.(*models.Gril)) continue } if first { first = false } else { idList += "," } idList += strconv.Itoa(v) } idList += ")" grils, err := m.getGrils("id IN " + idList) for _, g := range grils { list = append(list, g) } log.Printf("get_gril_from_ids: %dms", time.Since(t1).Nanoseconds()/1000000) return list, err } func (m *GrilsModule) viewGril(w http.ResponseWriter, r *http.Request, p httprouter.Params) { user, _ := m.g.Charakterin.GetUserFromRequest(r) el := m.g.EventLogger(r) id, err := util.ParseIDFromParams(p) if err != nil { http.Redirect(w, r, "/", 302) return } gril, err := m.FromID(id) if err != nil { http.Redirect(w, r, "/", 302) return } if err := m.GetListsOfGril(gril); err != nil { log.Println(err) } data := m.g.Renderer.DefaultData() data["user"] = user data["gril"] = gril // ähnliche grils holen rows, err := m.g.DB.Query(`SELECT gril_id FROM ( SELECT DISTINCT gril_id FROM grils_appearances WHERE appearance_id IN (SELECT appearance_id FROM grils_appearances WHERE gril_id = $1) ) as t ORDER BY RANDOM() LIMIT 4;`, id) if err != nil { log.Println("could not get similar grils:", err) http.Error(w, "500", http.StatusInternalServerError) return } defer rows.Close() var similar []*models.Gril for rows.Next() { var id int if err := rows.Scan(&id); err != nil { log.Println("error scanning for similar gril:", err) continue } g, err := m.FromID(id) if err != nil { log.Println("invalid similar gril:", err) continue } similar = append(similar, g) } data["SimilarGrils"] = similar m.g.Renderer.RenderPage("gril", w, data) el.ViewGril(user, eventlogging.ViewGrilData{ GrilID: gril.ID, }) } func pgArray(array []byte) []string { var results []string matches := pgArrayReg.FindAllStringSubmatch(string(array), -1) for _, match := range matches { s := match[pgValueIdx] s = strings.Trim(s, "\"") results = append(results, s) } return results } func New() *GrilsModule { return &GrilsModule{} }