add initial bits for an elm frontend

This commit is contained in:
Aaron Bieber 2023-04-23 19:12:26 -06:00
parent 2fcac629fe
commit 4ad5b2964a
No known key found for this signature in database
9 changed files with 446 additions and 203 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ result
gostart
.direnv
*.db
elm-stuff

27
elm.json Normal file
View File

@ -0,0 +1,27 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3"
},
"indirect": {
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.3"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View File

@ -44,7 +44,8 @@
sqlc
sqlite
rlwrap
nodePackages.typescript
elmPackages.elm
elmPackages.elm-live
];
};
});

View File

@ -117,11 +117,9 @@ func watchitemGET(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusUnprocessableEntity), http.StatusUnprocessableEntity)
return
}
watches, err := app.queries.GetAllWatchItemsByOwner(app.ctx, ownerID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
watches := app.watches.forID(ownerID)
wJson, err := json.Marshal(watches)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

41
main.go
View File

@ -50,6 +50,23 @@ func main() {
log.Fatal("can't open database: ", err)
}
dbExists := false
if *dbFile == ":memory:" {
err := tmpDBPopulate(db)
if err != nil {
log.Fatal(err)
}
dbExists = true
} else {
if _, err := os.Stat(*dbFile); os.IsNotExist(err) {
log.Println("Creating database..")
if _, err := db.ExecContext(app.ctx, schema); err != nil {
log.Fatal("can't create database schema: ", err)
}
}
dbExists = true
}
app.watches = &WatchResults{}
app.queries = data.New(db)
app.tsServer = &tsnet.Server{
@ -60,24 +77,12 @@ func main() {
log.Fatal("can't get ts local client: ", err)
}
dbExists := false
if *dbFile == ":memory:" {
err := tmpDBPopulate(db)
if err != nil {
log.Fatal(err)
}
} else {
if _, err := os.Stat(*dbFile); os.IsNotExist(err) {
log.Println("Creating database..")
if _, err := db.ExecContext(app.ctx, schema); err != nil {
log.Fatal("can't create database schema: ", err)
}
}
}
go func() {
time.Sleep(6 * time.Second)
dbExists = true
}()
/*
go func() {
time.Sleep(6 * time.Second)
dbExists = true
}()
*/
if *key != "" {
keyData, err := os.ReadFile(*key)

179
page.go
View File

@ -1,100 +1,12 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"sort"
"time"
"suah.dev/gostart/data"
"tailscale.com/tailcfg"
)
const gqEndPoint = "https://api.github.com/graphql"
const graphQuery = `
{
search(
query: "is:open is:public archived:false repo:%s in:title %s",
type: ISSUE,
first: 20
) {
issueCount
edges {
node {
... on Issue {
number
title
url
repository {
nameWithOwner
}
createdAt
}
... on PullRequest {
number
title
repository {
nameWithOwner
}
createdAt
url
}
}
}
}
rateLimit {
remaining
resetAt
}
}
`
type GQLQuery struct {
Query string `json:"query"`
}
func getData(q GQLQuery, token string) (*WatchResult, error) {
var req *http.Request
var err error
var re = &WatchResult{}
client := &http.Client{}
buf := new(bytes.Buffer)
if err := json.NewEncoder(buf).Encode(q); err != nil {
return nil, err
}
req, err = http.NewRequest("POST", gqEndPoint, buf)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", token))
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer func() {
err := res.Body.Close()
if err != nil {
log.Fatal("can't close body: ", err)
}
}()
if err = json.NewDecoder(res.Body).Decode(re); err != nil {
return nil, err
}
return re, nil
}
type Page struct {
Title string
System data.Owner
@ -114,94 +26,3 @@ func (p *Page) Sort() {
return p.PullRequests[i].Number > p.PullRequests[j].Number
})
}
type WatchResults []WatchResult
func (w *WatchResults) forID(ownerID int64) *WatchResults {
newResults := WatchResults{}
for _, r := range *w {
if r.OwnerID == ownerID {
newResults = append(newResults, r)
}
}
sort.Slice(newResults, func(i, j int) bool {
return newResults[i].Name < newResults[j].Name
})
return &newResults
}
func (w WatchResults) GetLimits() *RateLimit {
rl := &RateLimit{}
sort.Slice(w, func(i, j int) bool {
return w[i].Data.RateLimit.Remaining < w[j].Data.RateLimit.Remaining
})
if len(w) > 0 {
rl = &w[0].Data.RateLimit
}
return rl
}
func UpdateWatches(ghToken string) (*WatchResults, error) {
ctx := context.Background()
w := WatchResults{}
watches, err := app.queries.GetAllWatchItems(ctx)
if err != nil {
return nil, err
}
for _, watch := range watches {
qd := GQLQuery{Query: fmt.Sprintf(graphQuery, watch.Repo, watch.Name)}
wr, err := getData(qd, ghToken)
if err != nil {
return nil, err
}
wr.OwnerID = watch.OwnerID
wr.Name = watch.Name
wr.Repo = watch.Repo
sort.Slice(wr.Data.Search.Edges, func(i, j int) bool {
return wr.Data.Search.Edges[i].Node.CreatedAt.After(wr.Data.Search.Edges[j].Node.CreatedAt)
})
w = append(w, *wr)
}
sort.Slice(w, func(i, j int) bool {
return w[i].Name < w[j].Name
})
return &w, nil
}
type WatchResult struct {
Data Data `json:"data,omitempty"`
OwnerID int64
Name string
Repo string
}
type Repository struct {
NameWithOwner string `json:"nameWithOwner,omitempty"`
}
type Node struct {
Number int `json:"number,omitempty"`
Title string `json:"title,omitempty"`
Repository Repository `json:"repository,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
URL string `json:"url,omitempty"`
}
type Edges struct {
Node Node `json:"node,omitempty"`
}
type Search struct {
IssueCount int `json:"issueCount,omitempty"`
Edges []Edges `json:"edges,omitempty"`
}
type RateLimit struct {
Remaining int `json:"remaining,omitempty"`
ResetAt time.Time `json:"resetAt,omitempty"`
}
type Data struct {
Search Search `json:"search,omitempty"`
RateLimit RateLimit `json:"rateLimit,omitempty"`
}

34
src/Data.elm Normal file
View File

@ -0,0 +1,34 @@
module Data exposing (Edge, Link, Node, Watch, WatchData)
type alias Watch =
{ owner_id : Int
, name : String
, repo : String
, result_count : Int
}
type alias Node =
{ number : Int
}
type alias Edge =
{ node : Node
}
type alias WatchData =
{ search : List Edge
}
type alias Link =
{ id : Int
, owner_id : Int
, created_at : String
, name : String
, url : String
, logo_url : String
}

161
src/Main.elm Normal file
View File

@ -0,0 +1,161 @@
module Main exposing (..)
import Browser
import Html exposing (..)
import Html.Attributes exposing (style)
import Html.Events exposing (..)
import Http
import Json.Decode exposing (Decoder, field, int, list, map3, map4, map5, maybe, string)
main =
Browser.element
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
}
type Model
= Failure
| Loading
| Success (List Watch)
type alias Watch =
{ owner_id : Int
, name : String
, repo : String
, result_count : Int
}
type alias Node =
{ number : Int
}
type alias Edge =
{ node : Node
}
type alias WatchData =
{ search : List Edge
}
type alias Link =
{ id : Int
, owner_id : Int
, created_at : String
, name : String
, url : String
, logo_url : String
}
init : () -> ( Model, Cmd Msg )
init _ =
( Loading
, Cmd.batch
[ getWatches
]
)
type Msg
= MorePlease
| GetWatches (Result Http.Error (List Watch))
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
MorePlease ->
( Loading, getWatches )
GetWatches result ->
case result of
Ok watches ->
( Success watches, Cmd.none )
Err _ ->
( Failure, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
view : Model -> Html Msg
view model =
div []
[ h2 [] [ text "Watches" ]
, viewWatches model
]
viewWatches : Model -> Html Msg
viewWatches model =
case model of
Failure ->
div []
[ text "I can't load the watches"
, button [ onClick MorePlease ] [ text "Try agan!" ]
]
Loading ->
text "Loading..."
Success watches ->
div []
(List.map viewWatch watches)
viewLinks : Model -> Html Msg
viewLinks model =
case model of
Failure ->
div []
[ text "I can't load the links"
, button [ onClick MorePlease ] [ text "Try agan!" ]
]
Loading ->
text "Loading..."
Success links ->
text "success links..."
getWatches : Cmd Msg
getWatches =
Http.get
{ url = "/watches"
, expect = Http.expectJson GetWatches watchListDecoder
}
watchListDecoder : Decoder (List Watch)
watchListDecoder =
list watchDecoder
watchDecoder : Decoder Watch
watchDecoder =
map4 Watch
(field "owner_id" int)
(field "name" string)
(field "repo" string)
(field "result_count" int)
viewWatch : Watch -> Html Msg
viewWatch watch =
li []
[ text (String.fromInt watch.result_count ++ " " ++ watch.name)
, li [] [ text "butter" ]
]

195
watches.go Normal file
View File

@ -0,0 +1,195 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"sort"
"time"
)
const gqEndPoint = "https://api.github.com/graphql"
const graphQuery = `
{
search(
query: "is:open is:public archived:false repo:%s in:title %s",
type: ISSUE,
first: 20
) {
issueCount
edges {
node {
... on Issue {
number
title
url
repository {
nameWithOwner
}
createdAt
}
... on PullRequest {
number
title
repository {
nameWithOwner
}
createdAt
url
}
}
}
}
rateLimit {
remaining
resetAt
}
}
`
type GQLQuery struct {
Query string `json:"query"`
}
type WatchResults []WatchResult
func (w *WatchResults) forID(ownerID int64) *WatchResults {
newResults := WatchResults{}
for _, r := range *w {
if r.OwnerID == ownerID {
newResults = append(newResults, r)
}
}
sort.Slice(newResults, func(i, j int) bool {
return newResults[i].Name < newResults[j].Name
})
return &newResults
}
func (w WatchResults) GetLimits() *RateLimit {
rl := &RateLimit{}
sort.Slice(w, func(i, j int) bool {
return w[i].Data.RateLimit.Remaining < w[j].Data.RateLimit.Remaining
})
if len(w) > 0 {
rl = &w[0].Data.RateLimit
}
return rl
}
func UpdateWatches(ghToken string) (*WatchResults, error) {
ctx := context.Background()
w := WatchResults{}
watches, err := app.queries.GetAllWatchItems(ctx)
if err != nil {
return nil, err
}
for _, watch := range watches {
qd := GQLQuery{Query: fmt.Sprintf(graphQuery, watch.Repo, watch.Name)}
wr, err := getWatchData(qd, ghToken)
if err != nil {
return nil, err
}
wr.OwnerID = watch.OwnerID
wr.Name = watch.Name
wr.Repo = watch.Repo
wr.ResultCount = wr.Data.Search.IssueCount
for _, dr := range wr.Data.Search.Edges {
wr.Results = append(wr.Results, dr.Node)
}
sort.Slice(wr.Data.Search.Edges, func(i, j int) bool {
return wr.Data.Search.Edges[i].Node.CreatedAt.After(wr.Data.Search.Edges[j].Node.CreatedAt)
})
w = append(w, *wr)
}
sort.Slice(w, func(i, j int) bool {
return w[i].Name < w[j].Name
})
return &w, nil
}
type WatchResult struct {
Data Data `json:"data"`
OwnerID int64 `json:"owner_id"`
Name string `json:"name"`
Repo string `json:"repo"`
Results []Node `json:"results"`
ResultCount int `json:"result_count"`
}
type Repository struct {
NameWithOwner string `json:"nameWithOwner,omitempty"`
}
type Node struct {
Number int `json:"number,omitempty"`
Title string `json:"title,omitempty"`
Repository Repository `json:"repository,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
URL string `json:"url,omitempty"`
}
type Edges struct {
Node Node `json:"node,omitempty"`
}
type Search struct {
IssueCount int `json:"issueCount,omitempty"`
Edges []Edges `json:"edges,omitempty"`
}
type RateLimit struct {
Remaining int `json:"remaining,omitempty"`
ResetAt time.Time `json:"resetAt,omitempty"`
}
type Data struct {
Search Search `json:"search,omitempty"`
RateLimit RateLimit `json:"rateLimit,omitempty"`
}
func getWatchData(q GQLQuery, token string) (*WatchResult, error) {
var req *http.Request
var err error
var re = &WatchResult{}
client := &http.Client{}
buf := new(bytes.Buffer)
if err := json.NewEncoder(buf).Encode(q); err != nil {
return nil, err
}
req, err = http.NewRequest("POST", gqEndPoint, buf)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", token))
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer func() {
err := res.Body.Close()
if err != nil {
log.Fatal("can't close body: ", err)
}
}()
if err = json.NewDecoder(res.Body).Decode(re); err != nil {
return nil, err
}
return re, nil
}