package app import ( "database/sql" "fmt" "log" "net/http" "os" "path/filepath" "strings" sbv "github.com/chompy/ffxiv_strat_board_viewer" ) const boardPrefix = "[stgy:a" const boardPageTemplate = "strategy_board.html" const boardImageExt = "png" func getStrategyBoardImagePathByID(ID int64) string { return filepath.Join(strategyBoardImagePath, fmt.Sprintf("%d.%s", ID, boardImageExt)) } func getStrategyBoardURL(ID int64) string { return fmt.Sprintf("/b/%s", encodeIDToBase36(ID)) } func getStrategyBoardImageURL(ID int64) string { return fmt.Sprintf("/b/%s.%s", encodeIDToBase36(ID), boardImageExt) } func hasRenderedStrategyBoard(ID int64) bool { _, err := os.Stat(getStrategyBoardImagePathByID(ID)) return !os.IsNotExist(err) } func renderStrategyBoard(ID int64, board sbv.Board) error { log.Printf("Render strategy board %d", ID) // render board canvas, err := sbv.Draw(board) if err != nil { log.Printf("ERROR: %s", err.Error()) return err } // save image os.MkdirAll(strategyBoardImagePath, 0777) err = canvas.SavePNG(getStrategyBoardImagePathByID(ID)) if err != nil { log.Printf("ERROR: %s", err.Error()) return err } return nil } func processStrategyBoard(input string, created string) (StrategyBoardRecord, error) { // parse and process share code // hash board share code boardHash := hashString(input) log.Printf("Received strategy board with hash %s", boardHash) // use hash to locate existing record, err := fetchStrategyBoardRecordByHash(db, boardHash) if err != nil && err != sql.ErrNoRows { return record, err } // parse + save + render if record.ID == 0 || !hasRenderedStrategyBoard(record.ID) { // parse board share code boardData, err := sbv.Load(input) if err != nil { return record, err } // save new board data to database if record.ID == 0 { record = StrategyBoardRecord{ CreatedBy: created, Name: boardData.Name, Data: input, Hash: boardHash, } if err := createStrategyBoardRecord(db, &record); err != nil { return record, err } } // render board image if err := renderStrategyBoard(record.ID, boardData); err != nil { return record, err } } return record, nil } func httpHandleStrategyBoardImage(w http.ResponseWriter, r *http.Request, ID int64) { pathTo := getStrategyBoardImagePathByID(ID) log.Printf(" >> serve strategy board image %s", pathTo) w.Header().Set("Content-Type", fmt.Sprintf("image/%s", boardImageExt)) w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") http.ServeFile(w, r, pathTo) } func httpHandleStrategyBoardCode(w http.ResponseWriter, r *http.Request, code string) { boardRecord, err := processStrategyBoard(strings.TrimSuffix(code, "."+boardImageExt), getUserID(r)) if err != nil { httpHandleError(w, err) return } if strings.HasSuffix(code, "."+boardImageExt) { httpHandleRedirect(w, r, getStrategyBoardImageURL(boardRecord.ID)) return } httpHandleRedirect(w, r, getStrategyBoardURL(boardRecord.ID)) } func httpHandleStrategyBoardID(w http.ResponseWriter, r *http.Request, ID int64) { boardRecord, err := fetchStrategyBoardRecordByID(db, ID) if err != nil { httpHandleError(w, errHttpNotFound) return } // regenerate image if r.URL.Query().Get("regenerate") != "" { os.Remove(getStrategyBoardImagePathByID(boardRecord.ID)) if _, err := processStrategyBoard(boardRecord.Data, boardRecord.CreatedBy); err != nil { httpHandleError(w, err) return } } recordDetails, err := fetchRecordDetails(StrategyBoardRecord{}, boardRecord.ID) if err != nil { httpHandleError(w, err) return } httpHandleTemplate(w, boardPageTemplate, map[string]any{ "id": boardRecord.ID, "name": boardRecord.Name, "code": boardRecord.Data, "url": fmt.Sprintf("//%s%s", r.Host, getStrategyBoardURL(boardRecord.ID)), "image": fmt.Sprintf("//%s%s", r.Host, getStrategyBoardImageURL(boardRecord.ID)), "recordDetails": recordDetails, }) } func httpBaseHandleStrategyBoard(w http.ResponseWriter, r *http.Request, path []string) bool { input := path[len(path)-1] // import board via share code if strings.HasPrefix(input, boardPrefix) { httpHandleStrategyBoardCode(w, r, input) return true } // access existing board if path[0] == "b" { boardID := getIDFromBase36(strings.TrimSuffix(input, "."+boardImageExt)) // board image if strings.HasSuffix(input, "."+boardImageExt) { httpHandleStrategyBoardImage(w, r, boardID) return true } // board page httpHandleStrategyBoardID(w, r, boardID) return true } return false }