start API

Signed-off-by: Xe <me@christine.website>
This commit is contained in:
Cadey Ratio 2022-01-17 13:55:31 +00:00
parent 6da7a3d245
commit 84ca87befa
6 changed files with 156 additions and 10 deletions

View File

@ -81,6 +81,8 @@ func main() {
}
defer db.Close()
ln.Log(ctx, ln.Info("starting up..."), ln.F{"db": *dbLoc, "hostname": hostname, "ts-log-loc": *tsLogLoc})
done, tsLgr, err := getLogFout(*tsLogLoc)
if err != nil {
ln.FatalErr(ctx, err)

View File

@ -37,3 +37,9 @@ CREATE TABLE IF NOT EXISTS twitch_revenue
-- generated columns
, total_revenue REAL GENERATED ALWAYS AS (sub_revenue + prime_revenue + gifted_subs_revenue + multi_month_gifted_subs_revenue + bits_revenue + ad_revenue + game_sales_revenue + extensions_revenue + bounties_revenue)
);
CREATE TABLE IF NOT EXISTS sql_queries
( name TEXT PRIMARY KEY
, query TEXT UNIQUE NOT NULL
, who TEXT NOT NULL
);

118
server.go
View File

@ -4,16 +4,34 @@ import (
"context"
"crypto/tls"
"database/sql"
"embed"
"encoding/json"
"net/http"
"time"
"tailscale.com/client/tailscale"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/tsnet"
"within.website/ln"
"within.website/ln/ex"
)
//go:embed static/*
var staticFS embed.FS
func withWhois(next func(whois *apitype.WhoIsResponse, w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userInfo, err := tailscale.WhoIs(r.Context(), r.RemoteAddr)
if err != nil {
http.Error(w, "can't get whois response: "+err.Error(), http.StatusInternalServerError)
ln.Error(r.Context(), err)
return
}
next(userInfo, w, r)
})
}
type Server struct {
db *sql.DB
mux *http.ServeMux
@ -30,6 +48,9 @@ func NewServer(db *sql.DB, srv *tsnet.Server) *Server {
}
mux.HandleFunc("/api/whois", s.whois)
mux.HandleFunc("/api/importcsv", s.importCSV)
mux.HandleFunc("/api/queries/get", s.getQueries)
mux.Handle("/static/", http.FileServer(http.FS(staticFS)))
return s
}
@ -60,7 +81,7 @@ func (s *Server) ListenAndServe() error {
return hs.Serve(l)
}
func (s Server) whois(w http.ResponseWriter, r *http.Request) {
func (s *Server) whois(w http.ResponseWriter, r *http.Request) {
userInfo, err := tailscale.WhoIs(r.Context(), r.RemoteAddr)
if err != nil {
http.Error(w, "can't get whois response: "+err.Error(), http.StatusInternalServerError)
@ -69,9 +90,104 @@ func (s Server) whois(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(userInfo)
}
func (s *Server) addQuery(whois *apitype.WhoIsResponse, w http.ResponseWriter, r *http.Request) {
type AddQueryRequest struct {
Name string `json:"name"`
Query string `json:"query"`
}
var aqr AddQueryRequest
err := json.NewDecoder(r.Body).Decode(&aqr)
if err != nil {
ln.Error(r.Context(), err)
http.Error(w, "bad json", http.StatusBadRequest)
return
}
who := whois.UserProfile.LoginName
_, err = s.db.Exec("INSERT INTO sql_queries(name, query, who) VALUES (?, ?, ?)", aqr.Name, aqr.Query, who)
if err != nil {
ln.Error(r.Context(), err)
http.Error(w, "can't insert query", http.StatusInternalServerError)
return
}
}
func (s *Server) getQueries(w http.ResponseWriter, r *http.Request) {
type SQLQuery struct {
Name string `json:"name"`
Query string `json:"query"`
Who string `json:"who"`
}
var queries []SQLQuery
rows, err := s.db.QueryContext(r.Context(), "SELECT name, query, who FROM sql_queries")
if err != nil {
http.Error(w, "can't get query rows", http.StatusInternalServerError)
ln.Error(r.Context(), err)
return
}
defer rows.Close()
for rows.Next() {
var sq SQLQuery
err := rows.Scan(&sq.Name, &sq.Query, &sq.Who)
if err != nil {
http.Error(w, "can't get query row", http.StatusInternalServerError)
ln.Error(r.Context(), err)
}
queries = append(queries, sq)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(queries)
}
// curl https://twitchalitics.shark-harmonic.ts.net/api/importcsv -v -F csv=@"Channel Analytics and Revenue by day from Dec_19_2021 to Jan_17_2022.csv"
func (s *Server) importCSV(w http.ResponseWriter, r *http.Request) {
tx, err := s.db.BeginTx(r.Context(), nil)
if err != nil {
ln.Error(r.Context(), err)
http.Error(w, "can't hit db", http.StatusInternalServerError)
return
}
defer tx.Rollback()
r.ParseMultipartForm(10 << 20)
file, handler, err := r.FormFile("csv")
if err != nil {
ln.Error(r.Context(), err)
http.Error(w, "can't read file", http.StatusInternalServerError)
}
ln.WithF(r.Context(), ln.F{"filename": handler.Filename, "size": handler.Size})
rows, err := ReadRows(file)
for _, row := range rows {
err := row.Upsert(tx)
if err != nil {
ln.Error(r.Context(), err)
http.Error(w, "can't upsert row", http.StatusInternalServerError)
return
}
}
err = tx.Commit()
if err != nil {
ln.Error(r.Context(), err)
http.Error(w, "can't commit data", http.StatusInternalServerError)
}
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
userInfo, err := tailscale.WhoIs(r.Context(), r.RemoteAddr)
if err != nil {

15
static/importcsv.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Import CSV</title>
</head>
<body>
<form enctype="multipart/form-data" action="/api/csvimport" method="post">
<input type="file" name="csv" />
<input type="submit" value="upload" />
</form>
</body>
</html>

4
static/js/main.js Normal file
View File

@ -0,0 +1,4 @@
import { h, g, x, r } from "./xeact.js";
import { div, span, h1 } from "./xeact-html.js";

View File

@ -19,7 +19,13 @@ func ImportCSV(ctx context.Context, db *sql.DB, dir string) error {
}
for _, fname := range files {
rows, err := ReadRowsFromFile(fname)
fin, err := os.Open(fname)
if err != nil {
return fmt.Errorf("can't open file: %w", err)
}
defer fin.Close()
rows, err := ReadRows(fin)
if err != nil {
return err
}
@ -132,18 +138,15 @@ func (t TwitchRevenueCSV) Upsert(tx *sql.Tx) error {
return err
}
func ReadRowsFromFile(fname string) ([]*TwitchRevenueCSV, error) {
func ReadRows(fin io.Reader) ([]*TwitchRevenueCSV, error) {
var result []*TwitchRevenueCSV
fin, err := os.Open(fname)
if err != nil {
return nil, fmt.Errorf("can't open file: %w", err)
}
defer fin.Close()
rdr := csv.NewReader(fin)
_, err = rdr.Read() // discard header row
_, err := rdr.Read() // discard header row
if err != nil {
return nil, err
}
var n = 1
for {