2022-11-29 19:55:00 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
|
|
|
"database/sql"
|
|
|
|
"embed"
|
|
|
|
"flag"
|
2022-12-02 20:53:05 -07:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2022-11-29 19:55:00 -07:00
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/go-chi/chi/v5/middleware"
|
2022-12-02 20:53:05 -07:00
|
|
|
"github.com/go-chi/render"
|
2022-11-29 19:55:00 -07:00
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
"suah.dev/gostart/data"
|
|
|
|
"tailscale.com/client/tailscale"
|
|
|
|
"tailscale.com/tsnet"
|
|
|
|
)
|
|
|
|
|
|
|
|
//go:embed schema.sql
|
|
|
|
var schema string
|
|
|
|
|
|
|
|
//go:embed templates
|
|
|
|
var templates embed.FS
|
|
|
|
|
|
|
|
//go:embed assets
|
|
|
|
var assets embed.FS
|
|
|
|
|
|
|
|
var app = &App{
|
|
|
|
ctx: context.Background(),
|
|
|
|
tsServer: &tsnet.Server{},
|
|
|
|
tsLocalClient: &tailscale.LocalClient{},
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
name := flag.String("name", "startpage", "name of service")
|
|
|
|
key := flag.String("key", "", "path to file containing the api key")
|
2022-12-07 10:58:53 -07:00
|
|
|
watchInterval := flag.Int64("refresh", 5, "number of minutes between watch refresh")
|
2022-12-02 20:53:05 -07:00
|
|
|
dbFile := flag.String("db", ":memory:", "path to on-disk database file")
|
2022-11-29 19:55:00 -07:00
|
|
|
tokenFile := flag.String("auth", "", "path to file containing GH auth token")
|
|
|
|
flag.Parse()
|
|
|
|
|
2022-12-02 20:53:05 -07:00
|
|
|
db, err := sql.Open("sqlite", fmt.Sprintf("%s?cache=shared&mode=rwc", *dbFile))
|
|
|
|
if err != nil {
|
2022-12-05 15:45:39 -07:00
|
|
|
log.Fatal("can't open database: ", err)
|
2022-11-29 19:55:00 -07:00
|
|
|
}
|
|
|
|
|
2022-12-21 19:35:05 -07:00
|
|
|
app.watches = &WatchResults{}
|
2022-11-29 19:55:00 -07:00
|
|
|
app.queries = data.New(db)
|
|
|
|
app.tsServer = &tsnet.Server{
|
|
|
|
Hostname: *name,
|
|
|
|
}
|
|
|
|
app.tsLocalClient, err = app.tsServer.LocalClient()
|
|
|
|
if err != nil {
|
2022-12-05 15:45:39 -07:00
|
|
|
log.Fatal("can't get ts local client: ", err)
|
2022-11-29 19:55:00 -07:00
|
|
|
}
|
|
|
|
|
2022-12-21 19:23:44 -07:00
|
|
|
dbExists := false
|
2022-12-02 20:53:05 -07:00
|
|
|
if *dbFile == ":memory:" {
|
2022-12-21 19:23:44 -07:00
|
|
|
err := tmpDBPopulate(db)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2022-12-02 20:53:05 -07:00
|
|
|
} else {
|
|
|
|
if _, err := os.Stat(*dbFile); os.IsNotExist(err) {
|
|
|
|
log.Println("Creating database..")
|
|
|
|
if _, err := db.ExecContext(app.ctx, schema); err != nil {
|
2022-12-05 15:45:39 -07:00
|
|
|
log.Fatal("can't create database schema: ", err)
|
2022-12-02 20:53:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-21 19:23:44 -07:00
|
|
|
go func() {
|
|
|
|
time.Sleep(6 * time.Second)
|
|
|
|
dbExists = true
|
|
|
|
}()
|
2022-11-29 19:55:00 -07:00
|
|
|
|
|
|
|
if *key != "" {
|
|
|
|
keyData, err := os.ReadFile(*key)
|
|
|
|
if err != nil {
|
2022-12-05 15:45:39 -07:00
|
|
|
log.Fatal("can't read key file: ", err)
|
2022-11-29 19:55:00 -07:00
|
|
|
}
|
|
|
|
app.tsServer.AuthKey = string(keyData)
|
|
|
|
}
|
|
|
|
|
|
|
|
ln, err := app.tsServer.Listen("tcp", ":443")
|
|
|
|
if err != nil {
|
2022-12-05 15:45:39 -07:00
|
|
|
log.Fatal("can't listen: ", err)
|
2022-11-29 19:55:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
err := app.tsServer.Close()
|
|
|
|
if err != nil {
|
2022-12-05 15:45:39 -07:00
|
|
|
log.Fatal("can't close ts server: ", err)
|
2022-11-29 19:55:00 -07:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
fileServer := http.FileServer(http.FS(assets))
|
|
|
|
r := chi.NewRouter()
|
|
|
|
|
|
|
|
r.Use(middleware.Logger)
|
|
|
|
r.Use(OwnerCtx)
|
|
|
|
|
|
|
|
r.Mount("/assets", fileServer)
|
|
|
|
r.Route("/", func(r chi.Router) {
|
|
|
|
r.Get("/", index)
|
|
|
|
})
|
|
|
|
r.Route("/pullrequests", func(r chi.Router) {
|
|
|
|
r.Use(render.SetContentType(render.ContentTypeJSON))
|
|
|
|
r.Get("/", pullrequestsGET)
|
|
|
|
r.Delete("/{prID:[0-9]+}", pullrequestsDELETE)
|
|
|
|
r.Post("/", pullrequestsPOST)
|
|
|
|
})
|
|
|
|
r.Route("/links", func(r chi.Router) {
|
|
|
|
r.Use(render.SetContentType(render.ContentTypeJSON))
|
|
|
|
r.Get("/", linksGET)
|
2022-12-02 20:53:05 -07:00
|
|
|
r.Delete("/{linkID:[0-9]+}", linkDELETE)
|
2022-11-29 19:55:00 -07:00
|
|
|
r.Post("/", linksPOST)
|
|
|
|
})
|
|
|
|
r.Route("/watches", func(r chi.Router) {
|
|
|
|
r.Use(render.SetContentType(render.ContentTypeJSON))
|
|
|
|
r.Get("/", watchitemGET)
|
2022-12-02 20:53:05 -07:00
|
|
|
r.Delete("/{watchID:[0-9]+}", watchitemDELETE)
|
|
|
|
r.Post("/", watchitemPOST)
|
|
|
|
})
|
2022-12-04 19:03:59 -07:00
|
|
|
r.Route("/prignores", func(r chi.Router) {
|
|
|
|
r.Use(render.SetContentType(render.ContentTypeJSON))
|
|
|
|
r.Post("/", prignorePOST)
|
|
|
|
})
|
2022-12-02 20:53:05 -07:00
|
|
|
r.Route("/icons", func(r chi.Router) {
|
2022-12-05 20:45:42 -07:00
|
|
|
r.Use(IconCacher)
|
2022-12-02 20:53:05 -07:00
|
|
|
r.Get("/{linkID:[0-9]+}", iconGET)
|
2022-11-29 19:55:00 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
ghToken := os.Getenv("GH_AUTH_TOKEN")
|
|
|
|
|
|
|
|
if *tokenFile != "" && ghToken == "" {
|
|
|
|
tfBytes, err := os.ReadFile(*tokenFile)
|
|
|
|
if err != nil {
|
2022-12-05 15:45:39 -07:00
|
|
|
log.Fatal("can't read token file: ", err)
|
2022-11-29 19:55:00 -07:00
|
|
|
}
|
|
|
|
ghToken = string(tfBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
2022-12-06 20:03:32 -07:00
|
|
|
for {
|
2022-12-21 19:35:05 -07:00
|
|
|
if dbExists && ghToken != "" {
|
2022-12-21 19:23:44 -07:00
|
|
|
var err error
|
|
|
|
app.watches, err = UpdateWatches(ghToken)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("can't update watches: ", err)
|
|
|
|
}
|
|
|
|
time.Sleep(time.Duration(*watchInterval) * time.Minute)
|
|
|
|
} else {
|
|
|
|
time.Sleep(3 * time.Second)
|
2022-12-06 20:03:32 -07:00
|
|
|
}
|
2022-11-29 19:55:00 -07:00
|
|
|
}
|
2022-12-06 20:03:32 -07:00
|
|
|
|
2022-11-29 19:55:00 -07:00
|
|
|
}()
|
|
|
|
|
2022-12-02 20:53:05 -07:00
|
|
|
go func() {
|
2022-12-21 19:23:44 -07:00
|
|
|
if dbExists {
|
|
|
|
for {
|
|
|
|
links, err := app.queries.GetAllLinks(app.ctx)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("can't get links: ", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, link := range links {
|
|
|
|
fmt.Println(link.LogoUrl)
|
|
|
|
if link.LogoUrl == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
resp, err := http.Get(link.LogoUrl)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
err = resp.Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
contentType := resp.Header.Get("Content-Type")
|
|
|
|
|
|
|
|
err = app.queries.AddIcon(app.ctx, data.AddIconParams{
|
|
|
|
OwnerID: link.OwnerID,
|
|
|
|
LinkID: link.ID,
|
|
|
|
ContentType: contentType,
|
|
|
|
Data: body,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("can't add icon: ", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
time.Sleep(24 * time.Hour)
|
2022-12-02 20:53:05 -07:00
|
|
|
}
|
2022-12-21 19:23:44 -07:00
|
|
|
} else {
|
|
|
|
time.Sleep(3 * time.Second)
|
2022-12-02 20:53:05 -07:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2022-11-29 19:55:00 -07:00
|
|
|
hs := &http.Server{
|
|
|
|
Handler: r,
|
|
|
|
TLSConfig: &tls.Config{
|
|
|
|
GetCertificate: app.tsLocalClient.GetCertificate,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Panic(hs.ServeTLS(ln, "", ""))
|
|
|
|
}
|