Delete records and stuff
This commit is contained in:
@@ -25,6 +25,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -76,6 +77,11 @@ func (s *ScreenHome) Init(a *App) {
|
|||||||
s.style = a.style
|
s.style = a.style
|
||||||
|
|
||||||
s.alert = w.NewAlert("expds.alert", s.style)
|
s.alert = w.NewAlert("expds.alert", s.style)
|
||||||
|
s.alert.SetOkPressed(func() bool {
|
||||||
|
s.alert.SetVisible(false)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
s.alert.SetCancelPressed(nil)
|
||||||
|
|
||||||
s.openPdsEntry = w.NewField("home.openpds.field", s.style)
|
s.openPdsEntry = w.NewField("home.openpds.field", s.style)
|
||||||
s.openPdsEntry.SetLabel("ID")
|
s.openPdsEntry.SetLabel("ID")
|
||||||
@@ -131,6 +137,7 @@ func (s *ScreenHome) Init(a *App) {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
s.jsonContent.SetKeyMap(km)
|
s.jsonContent.SetKeyMap(km)
|
||||||
|
s.jsonContent.SetVimMode(viper.GetBool(data.KeyVimMode))
|
||||||
s.jsonContent.SetBorder(
|
s.jsonContent.SetBorder(
|
||||||
[]rune{'─', '┐', '│', '┘', '─', '─', ' ', '─', '├', '─', '┤', '┬', '│', '┴', '┼'},
|
[]rune{'─', '┐', '│', '┘', '─', '─', ' ', '─', '├', '─', '┤', '┬', '│', '┴', '┼'},
|
||||||
)
|
)
|
||||||
@@ -158,17 +165,16 @@ func (s *ScreenHome) GetName() string { return "home" }
|
|||||||
func (s *ScreenHome) HandleResize(ev *tcell.EventResize) {
|
func (s *ScreenHome) HandleResize(ev *tcell.EventResize) {
|
||||||
s.w, s.h = ev.Size()
|
s.w, s.h = ev.Size()
|
||||||
s.menuLayout.HandleResize(w.Coord{X: s.w, Y: s.h - 1}.ResizeEvent())
|
s.menuLayout.HandleResize(w.Coord{X: s.w, Y: s.h - 1}.ResizeEvent())
|
||||||
s.alert.HandleResize(ev)
|
s.alert.HandleResize(w.Coord{X: s.w / 2, Y: s.h / 2}.ResizeEvent())
|
||||||
|
s.alert.SetPos(w.Coord{X: s.w / 4, Y: s.h / 4})
|
||||||
|
|
||||||
s.status.SetPos(w.Coord{X: 0, Y: s.h - 1})
|
s.status.SetPos(w.Coord{X: 0, Y: s.h - 1})
|
||||||
s.status.HandleResize(w.Coord{X: s.w, Y: 1}.ResizeEvent())
|
s.status.HandleResize(w.Coord{X: s.w, Y: 1}.ResizeEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScreenHome) HandleKey(ev *tcell.EventKey) bool {
|
func (s *ScreenHome) HandleKey(ev *tcell.EventKey) bool {
|
||||||
if s.showAlert {
|
if s.alert.Visible() {
|
||||||
handled := s.alert.HandleKey(ev)
|
return s.alert.HandleKey(ev)
|
||||||
s.alert.SetMessage(fmt.Sprintf("Alert Handled? %v -> %s", handled, ev.Name()))
|
|
||||||
return handled
|
|
||||||
}
|
}
|
||||||
if ev.Key() == tcell.KeyF12 {
|
if ev.Key() == tcell.KeyF12 {
|
||||||
s.toggleCli()
|
s.toggleCli()
|
||||||
@@ -192,7 +198,14 @@ func (s *ScreenHome) HandleKey(ev *tcell.EventKey) bool {
|
|||||||
}
|
}
|
||||||
return s.openPdsEntry.HandleKey(ev)
|
return s.openPdsEntry.HandleKey(ev)
|
||||||
}
|
}
|
||||||
|
switch ev.Key() {
|
||||||
|
case tcell.KeyCtrlO:
|
||||||
|
return s.menuOpenPds()
|
||||||
|
case tcell.KeyCtrlD:
|
||||||
|
return s.deleteCurrentRecord()
|
||||||
|
case tcell.KeyCtrlR:
|
||||||
|
return s.menuRefreshPds()
|
||||||
|
}
|
||||||
return s.menuLayout.HandleKey(ev)
|
return s.menuLayout.HandleKey(ev)
|
||||||
}
|
}
|
||||||
func (s *ScreenHome) HandleTime(ev *tcell.EventTime) {
|
func (s *ScreenHome) HandleTime(ev *tcell.EventTime) {
|
||||||
@@ -201,6 +214,10 @@ func (s *ScreenHome) HandleTime(ev *tcell.EventTime) {
|
|||||||
s.status.HandleTime(ev)
|
s.status.HandleTime(ev)
|
||||||
}
|
}
|
||||||
func (s *ScreenHome) Draw() {
|
func (s *ScreenHome) Draw() {
|
||||||
|
if s.alert.Visible() {
|
||||||
|
s.a.DrawWidget(s.alert)
|
||||||
|
return
|
||||||
|
}
|
||||||
if s.doOpen {
|
if s.doOpen {
|
||||||
s.menuLayout.SetStyle(s.style.Foreground(tcell.ColorGray))
|
s.menuLayout.SetStyle(s.style.Foreground(tcell.ColorGray))
|
||||||
}
|
}
|
||||||
@@ -208,6 +225,8 @@ func (s *ScreenHome) Draw() {
|
|||||||
m := s.menuLayout.Menu()
|
m := s.menuLayout.Menu()
|
||||||
s.a.DrawWidget(s.menuLayout)
|
s.a.DrawWidget(s.menuLayout)
|
||||||
if s.doOpen && !m.Active() {
|
if s.doOpen && !m.Active() {
|
||||||
|
x, y := s.pdsListing.GetX(), s.pdsListing.GetY()
|
||||||
|
s.a.DrawText(x+2, y, strings.Repeat(" ", len(s.pdsListing.Title())), s.style)
|
||||||
s.a.DrawWidget(s.openPdsEntry)
|
s.a.DrawWidget(s.openPdsEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +238,10 @@ func (s *ScreenHome) Draw() {
|
|||||||
}
|
}
|
||||||
func (s *ScreenHome) Exit() error { return nil }
|
func (s *ScreenHome) Exit() error { return nil }
|
||||||
func (s *ScreenHome) Log(t string, a ...any) {
|
func (s *ScreenHome) Log(t string, a ...any) {
|
||||||
|
if strings.Contains(fmt.Sprintf(t, a...), "auth server request failed") {
|
||||||
|
// This error happens even on successfully creating an oAuth request? Maybe it's just me
|
||||||
|
return
|
||||||
|
}
|
||||||
s.cli.Log(t, a...)
|
s.cli.Log(t, a...)
|
||||||
s.showCli()
|
s.showCli()
|
||||||
}
|
}
|
||||||
@@ -237,39 +260,13 @@ func (s *ScreenHome) initMenu() {
|
|||||||
inferRecNm := fmt.Sprintf("%s Infer Record Names", wrk)
|
inferRecNm := fmt.Sprintf("%s Infer Record Names", wrk)
|
||||||
s.menuLayout.AddMenuItems(
|
s.menuLayout.AddMenuItems(
|
||||||
s.menuLayout.CreateMenuItem("file", "File", nil, 'f',
|
s.menuLayout.CreateMenuItem("file", "File", nil, 'f',
|
||||||
s.menuLayout.CreateMenuItem("file.openpds", "Open PDS", func() bool {
|
s.menuLayout.CreateMenuItem("file.openpds", "Open PDS", s.menuOpenPds, 'o'),
|
||||||
s.menuLayout.ToggleMenu()
|
s.menuLayout.CreateMenuItem("file.reloadpds", "Reload PDS", s.menuRefreshPds, 'r'),
|
||||||
s.doOpen = true
|
s.menuLayout.CreateMenuItem("file.exit", "Exit", s.menuExit, 'x'),
|
||||||
s.pdsListing.SetTitle(strings.Repeat(" ", 30))
|
|
||||||
return true
|
|
||||||
}, 'o'),
|
|
||||||
s.menuLayout.CreateMenuItem("file.reloadpds", "Reload PDS", func() bool {
|
|
||||||
s.menuLayout.ToggleMenu()
|
|
||||||
if s.activePds == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s.cliGetPds("getpds", s.activePds.AtId.String())
|
|
||||||
}, 'r'),
|
|
||||||
s.menuLayout.CreateMenuItem("file.exit", "Exit", func() bool {
|
|
||||||
s.a.Exit()
|
|
||||||
return true
|
|
||||||
}, 'x'),
|
|
||||||
),
|
),
|
||||||
s.menuLayout.CreateMenuItem("settings", "Settings", nil, 's',
|
s.menuLayout.CreateMenuItem("settings", "Settings", nil, 's',
|
||||||
s.menuLayout.CreateMenuItem("settings.vimmode", vimText, func() bool {
|
s.menuLayout.CreateMenuItem("settings.vimmode", vimText, s.menuVimMode, 'v'),
|
||||||
s.menuLayout.ToggleMenu()
|
s.menuLayout.CreateMenuItem("settings.inferrecnm", inferRecNm, s.menuInferRecNm, 'r'),
|
||||||
viper.Set(data.KeyVimMode, !viper.GetBool(data.KeyVimMode))
|
|
||||||
viper.WriteConfig()
|
|
||||||
s.update()
|
|
||||||
return true
|
|
||||||
}, 'v'),
|
|
||||||
s.menuLayout.CreateMenuItem("settings.inferrecnm", inferRecNm, func() bool {
|
|
||||||
s.menuLayout.ToggleMenu()
|
|
||||||
viper.Set(data.KeyRecNmInfer, !viper.GetBool(data.KeyRecNmInfer))
|
|
||||||
viper.WriteConfig()
|
|
||||||
s.update()
|
|
||||||
return true
|
|
||||||
}, 'r'),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -337,41 +334,13 @@ func (s *ScreenHome) showCli() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScreenHome) cliGetPds(args ...string) bool {
|
func (s *ScreenHome) cliGetPds(args ...string) bool {
|
||||||
s.isLoading = true
|
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
s.Log("No id given.")
|
s.Log("No id given.")
|
||||||
s.Log("Usage: 'getpds <atproto id>'")
|
s.Log("Usage: 'getpds <atproto id>'")
|
||||||
s.isLoading = false
|
s.isLoading = false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
go func() {
|
s.getPds(args[1])
|
||||||
defer func() { s.isLoading = false }()
|
|
||||||
var pds *models.Pds
|
|
||||||
var err error
|
|
||||||
if s.activePds != nil && string(s.activePds.AtId) == args[1] {
|
|
||||||
pds, err = s.r.ReloadPds(args[1])
|
|
||||||
} else {
|
|
||||||
pds, err = s.r.GetPDS(args[1])
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
s.Log(err.Error())
|
|
||||||
return
|
|
||||||
} else if pds == nil {
|
|
||||||
s.Log("PDS (%s) Not Found.", args[1])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.doOpen = false
|
|
||||||
slog.Default().Debug(fmt.Sprintf("Retrieved: %s (%s) at %s", pds.AtId, pds.Did, time.Now().Format(time.RFC3339)))
|
|
||||||
s.activePds = pds
|
|
||||||
s.updatePdsListing()
|
|
||||||
n, err := s.pdsListing.GetActiveNode()
|
|
||||||
if err == nil && n != nil {
|
|
||||||
s.changePdsList(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.layout.ActivateWidget(s.columns)
|
|
||||||
s.columns.ActivateWidget(s.pdsListing)
|
|
||||||
}()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +352,8 @@ func (s *ScreenHome) cliAuthPds(args ...string) bool {
|
|||||||
s.isLoading = true
|
s.isLoading = true
|
||||||
go func() {
|
go func() {
|
||||||
defer func() { s.isLoading = false }()
|
defer func() { s.isLoading = false }()
|
||||||
|
// Check if we already have authentication.
|
||||||
|
if !s.r.Auth.HasAuth(s.activePds.Did) {
|
||||||
atid := s.activePds.AtId.String()
|
atid := s.activePds.AtId.String()
|
||||||
callbackRes := make(chan url.Values, 1)
|
callbackRes := make(chan url.Values, 1)
|
||||||
listenPort, err := s.r.Auth.ListenForCallback(callbackRes)
|
listenPort, err := s.r.Auth.ListenForCallback(callbackRes)
|
||||||
@@ -391,16 +362,22 @@ func (s *ScreenHome) cliAuthPds(args ...string) bool {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.Log("Listening on %d", listenPort)
|
s.Log("Listening on %d", listenPort)
|
||||||
var authUrl string
|
_, err = s.r.Auth.StartAuthFlow(listenPort, atid, callbackRes)
|
||||||
authUrl, err = s.r.Auth.StartAuthFlow(listenPort, atid, callbackRes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log("Error starting auth flow: %w", err)
|
s.Log("Error starting auth flow: %w", err)
|
||||||
}
|
} else {
|
||||||
s.alert.SetTitle("Authentication Started")
|
s.alert.SetTitle("Authentication Started")
|
||||||
s.alert.SetMessage(fmt.Sprintf("OAuth Process Started.\nIf a browser window didn't open, you can open this URL manually:\n%s", authUrl))
|
s.alert.SetMessage(fmt.Sprintf("OAuth Process Started.\nIf a browser window didn't open, you can open this URL manually:\nhttp://localhost:%d/auth\nThis url will redirect to the PDS OAuth Page.", listenPort))
|
||||||
s.alert.SetVisible(true)
|
s.alert.SetVisible(true)
|
||||||
s.alert.SetActive(true)
|
s.alert.SetActive(true)
|
||||||
s.showAlert = true
|
go func() {
|
||||||
|
for s.isLoading {
|
||||||
|
time.Sleep(100)
|
||||||
|
}
|
||||||
|
s.alert.SetVisible(false)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -518,6 +495,130 @@ func (s *ScreenHome) updateJsonView(tn *wd.TreeNode) bool {
|
|||||||
} else {
|
} else {
|
||||||
s.jsonContent.SetValue(s.activePds.Records[tn.Value()])
|
s.jsonContent.SetValue(s.activePds.Records[tn.Value()])
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScreenHome) menuOpenPds() bool {
|
||||||
|
s.menuLayout.HideMenu()
|
||||||
|
s.doOpen = true
|
||||||
|
s.pdsListing.SetTitle(strings.Repeat(" ", 30))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScreenHome) menuRefreshPds() bool {
|
||||||
|
s.menuLayout.HideMenu()
|
||||||
|
if s.activePds == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to persist 'opened' nodes
|
||||||
|
var opened []string
|
||||||
|
for _, n := range s.pdsListing.GetNodeList() {
|
||||||
|
if n.IsExpanded() {
|
||||||
|
opened = append(opened, n.Value())
|
||||||
|
s.Log("Opened: %s", n.Value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.getPds(s.activePds.AtId.String())
|
||||||
|
for _, n := range s.pdsListing.GetNodeList() {
|
||||||
|
s.Log("Testing: %s", n.Value())
|
||||||
|
if slices.Contains(opened, n.Value()) {
|
||||||
|
s.Log("-> Is Opened")
|
||||||
|
n.Expand()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScreenHome) menuExit() bool {
|
||||||
|
s.a.Exit()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScreenHome) menuVimMode() bool {
|
||||||
|
s.menuLayout.HideMenu()
|
||||||
|
viper.Set(data.KeyVimMode, !viper.GetBool(data.KeyVimMode))
|
||||||
|
viper.WriteConfig()
|
||||||
|
s.update()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScreenHome) menuInferRecNm() bool {
|
||||||
|
s.menuLayout.HideMenu()
|
||||||
|
viper.Set(data.KeyRecNmInfer, !viper.GetBool(data.KeyRecNmInfer))
|
||||||
|
viper.WriteConfig()
|
||||||
|
s.update()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScreenHome) getPds(atId string) {
|
||||||
|
s.isLoading = true
|
||||||
|
go func() {
|
||||||
|
defer func() { s.isLoading = false }()
|
||||||
|
var pds *models.Pds
|
||||||
|
var err error
|
||||||
|
if s.activePds != nil && string(s.activePds.AtId) == atId {
|
||||||
|
pds, err = s.r.ReloadPds(atId)
|
||||||
|
} else {
|
||||||
|
pds, err = s.r.GetPDS(atId)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log(err.Error())
|
||||||
|
return
|
||||||
|
} else if pds == nil {
|
||||||
|
s.Log("PDS (%s) Not Found.", atId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.doOpen = false
|
||||||
|
slog.Default().Debug(fmt.Sprintf("Retrieved: %s (%s) at %s", pds.AtId, pds.Did, time.Now().Format(time.RFC3339)))
|
||||||
|
s.activePds = pds
|
||||||
|
s.updatePdsListing()
|
||||||
|
n, err := s.pdsListing.GetActiveNode()
|
||||||
|
if err == nil && n != nil {
|
||||||
|
s.changePdsList(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.layout.ActivateWidget(s.columns)
|
||||||
|
s.columns.ActivateWidget(s.pdsListing)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScreenHome) deleteCurrentRecord() bool {
|
||||||
|
tn, err := s.pdsListing.GetActiveNode()
|
||||||
|
if err != nil {
|
||||||
|
s.Log("error getting active node")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tnPath := tn.GetValuePath()
|
||||||
|
if len(tnPath) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
//tnLabelPath := tn.GetLabelPath()
|
||||||
|
nsid, err := syntax.ParseNSID(tnPath[0])
|
||||||
|
if err != nil {
|
||||||
|
s.Log("error parsing NSID from %s: %w", tnPath[0], err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rkey := tnPath[1]
|
||||||
|
if err := s.r.DeleteRecord(s.activePds.Did, nsid, rkey); err != nil {
|
||||||
|
s.Log("error deleting record: %w", err)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
s.alert.SetTitle("Confirm Deletion")
|
||||||
|
s.alert.SetMessage(fmt.Sprintf("Are you sure you want to delete this record?\n%s %s\nThis cannot be undone.", tnLabelPath[0], tnLabelPath[1]))
|
||||||
|
s.alert.SetVisible(true)
|
||||||
|
s.alert.SetActive(true)
|
||||||
|
s.alert.SetOkPressed(func() bool {
|
||||||
|
s.r.DeleteRecord(s.activePds.Did, nsid, rkey)
|
||||||
|
s.alert.SetVisible(false)
|
||||||
|
s.alert.SetActive(false)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
s.alert.SetCancelPressed(func() bool {
|
||||||
|
s.alert.SetVisible(false)
|
||||||
|
s.alert.SetActive(false)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
*/
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
48
data/repo.go
48
data/repo.go
@@ -23,12 +23,10 @@ package data
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/expds/data/models"
|
"git.bullercodeworks.com/brian/expds/data/models"
|
||||||
"github.com/bluesky-social/indigo/atproto/syntax"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,52 +64,6 @@ func NewRepo() (*Repo, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repo) fetchPds(atId string) (*models.Pds, error) {
|
|
||||||
p, err := models.NewPdsFromDid(atId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.LoadedPDSs[atId] = p
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) ReloadPds(atId string) (*models.Pds, error) {
|
|
||||||
return r.fetchPds(atId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) GetPDS(atId string) (*models.Pds, error) {
|
|
||||||
if p, ok := r.LoadedPDSs[atId]; ok && time.Since(p.RefreshTime) < r.BestBy {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
return r.fetchPds(atId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) SendToPDS(did syntax.DID) error {
|
|
||||||
session, err := r.Auth.GetSession(did)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c := session.APIClient()
|
|
||||||
body := map[string]any{
|
|
||||||
"repo": c.AccountDID.String(),
|
|
||||||
"collection": "com.bullercodeworks.expds.status",
|
|
||||||
"record": map[string]any{
|
|
||||||
"$type": "com.bullercodeworks.expds.status",
|
|
||||||
"text": "writeable",
|
|
||||||
"createdAt": syntax.DatetimeNow(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var resp struct {
|
|
||||||
Uri syntax.ATURI `json:"uri"`
|
|
||||||
}
|
|
||||||
r.Logger.Debug("posting expds status...")
|
|
||||||
if err := c.Post(r.context, "com.atproto.repo.CreateRecord", body, &resp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Logger.Debug(fmt.Sprintf("posted: %s :: %s", resp.Uri.Authority(), resp.Uri.RecordKey()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) SetLogFunc(l func(string, ...any)) {
|
func (r *Repo) SetLogFunc(l func(string, ...any)) {
|
||||||
r.logFunc = l
|
r.logFunc = l
|
||||||
r.handler = NewAppLogHandler(r.logFunc)
|
r.handler = NewAppLogHandler(r.logFunc)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type AuthRepo struct {
|
|||||||
context context.Context
|
context context.Context
|
||||||
|
|
||||||
session *oauth.ClientSessionData
|
session *oauth.ClientSessionData
|
||||||
|
authUrl string
|
||||||
authError error
|
authError error
|
||||||
|
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
@@ -58,6 +59,7 @@ func (r *AuthRepo) buildOAuthClient() (*oauth.ClientConfig, *oauth.ClientApp, *S
|
|||||||
SessionExpiryDuration: time.Hour * 24 * 90,
|
SessionExpiryDuration: time.Hour * 24 * 90,
|
||||||
SessionInactivityDuration: time.Hour * 24 * 14,
|
SessionInactivityDuration: time.Hour * 24 * 14,
|
||||||
AuthRequestExpiryDuration: time.Minute * 30,
|
AuthRequestExpiryDuration: time.Minute * 30,
|
||||||
|
Logger: r.Logger,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
@@ -68,19 +70,20 @@ func (r *AuthRepo) buildOAuthClient() (*oauth.ClientConfig, *oauth.ClientApp, *S
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *AuthRepo) StartAuthFlow(port int, identifier string, callbackRes chan url.Values) (string, error) {
|
func (r *AuthRepo) StartAuthFlow(port int, identifier string, callbackRes chan url.Values) (string, error) {
|
||||||
|
var err error
|
||||||
r.oauthConfig.CallbackURL = fmt.Sprintf("http://127.0.0.1:%d/callback", port)
|
r.oauthConfig.CallbackURL = fmt.Sprintf("http://127.0.0.1:%d/callback", port)
|
||||||
authUrl, err := r.oauthClient.StartAuthFlow(r.context, identifier)
|
r.authUrl, err = r.oauthClient.StartAuthFlow(r.context, identifier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error logging in: %w", err)
|
return "", fmt.Errorf("error logging in: %w", err)
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(authUrl, "https://") {
|
if !strings.HasPrefix(r.authUrl, "https://") {
|
||||||
return "", fmt.Errorf("non-https authUrl")
|
return "", fmt.Errorf("non-https authUrl")
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
exec.Command("xdg-open", authUrl).Start()
|
exec.Command("xdg-open", r.authUrl).Start()
|
||||||
r.session, r.authError = r.oauthClient.ProcessCallback(r.context, <-callbackRes)
|
r.session, r.authError = r.oauthClient.ProcessCallback(r.context, <-callbackRes)
|
||||||
}()
|
}()
|
||||||
return authUrl, nil
|
return r.authUrl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Follows XDG conventions and creates the directories if necessary.
|
// Follows XDG conventions and creates the directories if necessary.
|
||||||
@@ -101,6 +104,12 @@ func (r *AuthRepo) ListenForCallback(res chan url.Values) (int, error) {
|
|||||||
Handler: mux,
|
Handler: mux,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mux.HandleFunc("/auth", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Header().Set("Location", r.authUrl)
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(303)
|
||||||
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/callback", func(w http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/callback", func(w http.ResponseWriter, req *http.Request) {
|
||||||
res <- req.URL.Query()
|
res <- req.URL.Query()
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
@@ -130,6 +139,6 @@ func (r *AuthRepo) GetSession(did syntax.DID) (*oauth.ClientSession, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting most recent session: %w", err)
|
return nil, fmt.Errorf("error getting most recent session: %w", err)
|
||||||
}
|
}
|
||||||
r.Logger.Warn(fmt.Sprintf("GetSession(): Resuming Session: %s (%s)", sess.SessionID, sess.AccountDID))
|
r.Logger.Debug(fmt.Sprintf("GetSession(): Resuming Session: %s (%s)", sess.SessionID, sess.AccountDID))
|
||||||
return r.oauthClient.ResumeSession(r.context, sess.AccountDID, sess.SessionID)
|
return r.oauthClient.ResumeSession(r.context, sess.AccountDID, sess.SessionID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package data
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluesky-social/indigo/atproto/auth/oauth"
|
"github.com/bluesky-social/indigo/atproto/auth/oauth"
|
||||||
@@ -10,6 +13,7 @@ import (
|
|||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Taken from https://github.com/bluesky-social/cookbook/blob/main/go-oauth-cli-app/sqlitestore.go
|
// Taken from https://github.com/bluesky-social/cookbook/blob/main/go-oauth-cli-app/sqlitestore.go
|
||||||
@@ -21,6 +25,7 @@ type SqliteStoreConfig struct {
|
|||||||
SessionExpiryDuration time.Duration // duration since session creation
|
SessionExpiryDuration time.Duration // duration since session creation
|
||||||
SessionInactivityDuration time.Duration // duration since last session update
|
SessionInactivityDuration time.Duration // duration since last session update
|
||||||
AuthRequestExpiryDuration time.Duration // duration since auth request creation
|
AuthRequestExpiryDuration time.Duration // duration since auth request creation
|
||||||
|
Logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the [oauth.ClientAuthStore] interface, backed by sqlite via gorm
|
// Implements the [oauth.ClientAuthStore] interface, backed by sqlite via gorm
|
||||||
@@ -65,7 +70,14 @@ func NewSqliteStore(cfg *SqliteStoreConfig) (*SqliteStore, error) {
|
|||||||
return nil, fmt.Errorf("missing AuthRequestExpiryDuration")
|
return nil, fmt.Errorf("missing AuthRequestExpiryDuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := gorm.Open(sqlite.Open(cfg.DatabasePath), &gorm.Config{})
|
db, err := gorm.Open(sqlite.Open(cfg.DatabasePath), &gorm.Config{
|
||||||
|
Logger: logger.New(
|
||||||
|
log.New(os.Stdout, "\r\n", log.LstdFlags),
|
||||||
|
logger.Config{
|
||||||
|
IgnoreRecordNotFoundError: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed opening db: %w", err)
|
return nil, fmt.Errorf("failed opening db: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
77
data/repo_pds.go
Normal file
77
data/repo_pds.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.bullercodeworks.com/brian/expds/data/models"
|
||||||
|
"github.com/bluesky-social/indigo/atproto/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Repo) fetchPds(atId string) (*models.Pds, error) {
|
||||||
|
p, err := models.NewPdsFromDid(atId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.LoadedPDSs[atId] = p
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) ReloadPds(atId string) (*models.Pds, error) {
|
||||||
|
return r.fetchPds(atId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) GetPDS(atId string) (*models.Pds, error) {
|
||||||
|
if p, ok := r.LoadedPDSs[atId]; ok && time.Since(p.RefreshTime) < r.BestBy {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
return r.fetchPds(atId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) SendToPDS(did syntax.DID) error {
|
||||||
|
session, err := r.Auth.GetSession(did)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c := session.APIClient()
|
||||||
|
body := map[string]any{
|
||||||
|
"repo": c.AccountDID.String(),
|
||||||
|
"collection": "com.bullercodeworks.expds.status",
|
||||||
|
"record": map[string]any{
|
||||||
|
"$type": "com.bullercodeworks.expds.status",
|
||||||
|
"text": "writeable",
|
||||||
|
"createdAt": syntax.DatetimeNow(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var resp struct {
|
||||||
|
Uri syntax.ATURI `json:"uri"`
|
||||||
|
}
|
||||||
|
r.Logger.Debug("posting expds status...")
|
||||||
|
if err := c.Post(r.context, "com.atproto.repo.createRecord", body, &resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Logger.Debug("posted: %s :: %s", resp.Uri.Authority(), resp.Uri.RecordKey())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) DeleteRecord(did syntax.DID, collection syntax.NSID, rkey string) error {
|
||||||
|
session, err := r.Auth.GetSession(did)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c := session.APIClient()
|
||||||
|
body := map[string]any{
|
||||||
|
"repo": c.AccountDID,
|
||||||
|
"collection": collection,
|
||||||
|
"rkey": rkey,
|
||||||
|
}
|
||||||
|
var resp struct {
|
||||||
|
Cid syntax.CID `json:"cid"`
|
||||||
|
Rev syntax.TID `json:"rev"`
|
||||||
|
}
|
||||||
|
r.Logger.Debug("deleting record (%s)", rkey)
|
||||||
|
if err := c.Post(r.context, "com.atproto.repo.deleteRecord", body, &resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Logger.Debug("posted: %s :: %s", resp.Cid.String(), resp.Rev.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -269,6 +269,9 @@ func (w *TreeBrowser) ClearBorder() { w.border = []rune{}
|
|||||||
func (w *TreeBrowser) SetOnChange(c func(*TreeNode) bool) { w.onChange = c }
|
func (w *TreeBrowser) SetOnChange(c func(*TreeNode) bool) { w.onChange = c }
|
||||||
func (w *TreeBrowser) SetOnSelect(s func(*TreeNode) bool) { w.onSelect = s }
|
func (w *TreeBrowser) SetOnSelect(s func(*TreeNode) bool) { w.onSelect = s }
|
||||||
func (w *TreeBrowser) SetVimMode(b bool) { w.vimMode = b }
|
func (w *TreeBrowser) SetVimMode(b bool) { w.vimMode = b }
|
||||||
|
|
||||||
|
func (w *TreeBrowser) GetNodeList() []*TreeNode { return w.nodes }
|
||||||
|
|
||||||
func (w *TreeBrowser) nmGetActiveNode() (*TreeNode, error) {
|
func (w *TreeBrowser) nmGetActiveNode() (*TreeNode, error) {
|
||||||
if len(w.listNodes) <= 0 {
|
if len(w.listNodes) <= 0 {
|
||||||
return nil, errors.New("no nodes")
|
return nil, errors.New("no nodes")
|
||||||
@@ -515,10 +518,16 @@ func (tn *TreeNode) GetLabelPath() []string {
|
|||||||
var path []string
|
var path []string
|
||||||
if tn.parent != nil {
|
if tn.parent != nil {
|
||||||
path = tn.parent.GetLabelPath()
|
path = tn.parent.GetLabelPath()
|
||||||
|
|
||||||
}
|
}
|
||||||
return append(path, tn.Label())
|
return append(path, tn.Label())
|
||||||
}
|
}
|
||||||
|
func (tn *TreeNode) GetValuePath() []string {
|
||||||
|
var path []string
|
||||||
|
if tn.parent != nil {
|
||||||
|
path = tn.parent.GetValuePath()
|
||||||
|
}
|
||||||
|
return append(path, tn.Value())
|
||||||
|
}
|
||||||
|
|
||||||
func (tn *TreeNode) getList() []string {
|
func (tn *TreeNode) getList() []string {
|
||||||
pre := strings.Repeat(tn.depthIndic, tn.Depth())
|
pre := strings.Repeat(tn.depthIndic, tn.Depth())
|
||||||
@@ -554,6 +563,9 @@ func (tn *TreeNode) SearchLabels(f string) *TreeNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tn *TreeNode) IsExpanded() bool { return tn.expanded }
|
||||||
|
func (tn *TreeNode) Expand() { tn.expanded = true }
|
||||||
|
func (tn *TreeNode) Collapse() { tn.expanded = false }
|
||||||
func (tn *TreeNode) ToggleExpand() { tn.expanded = !tn.expanded }
|
func (tn *TreeNode) ToggleExpand() { tn.expanded = !tn.expanded }
|
||||||
|
|
||||||
func (tn *TreeNode) AddChild(t *TreeNode, rest ...*TreeNode) {
|
func (tn *TreeNode) AddChild(t *TreeNode, rest ...*TreeNode) {
|
||||||
|
|||||||
Reference in New Issue
Block a user