backend for xcvr appview
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

add a bunch of crap esp. apropos signets

+359 -71
+1
migrations/001_init.up.sql
··· 36 36 37 37 CREATE TABLE signets ( 38 38 uri TEXT PRIMARY KEY, 39 + issuer_did TEXT NOT NULL, 39 40 did TEXT NOT NULL, 40 41 channel_uri TEXT NOT NULL, 41 42 FOREIGN KEY (channel_uri) REFERENCES channels(uri) ON DELETE CASCADE,
+17 -3
server/cmd/main.go
··· 7 7 "os" 8 8 "time" 9 9 "xcvr-backend/internal/atplistener" 10 + "xcvr-backend/internal/atputils" 10 11 "xcvr-backend/internal/db" 11 12 "xcvr-backend/internal/handler" 12 13 "xcvr-backend/internal/log" ··· 30 31 logger.Println("failed to init db") 31 32 panic(err) 32 33 } 33 - model.Init(store) 34 + host, err := atputils.GetPDSFromHandle(context.Background(), atputils.GetMyHandle()) 35 + if err != nil { 36 + panic(err) 37 + } 38 + did := atputils.GetMyDid() 39 + if did == "" { 40 + panic(errors.New("WOOPS I MESSED UP THE DID")) 41 + } 42 + xrpc := oauth.NewPasswordClient(did, host, logger) 43 + err = xrpc.CreateSession(context.Background()) 44 + if err != nil { 45 + panic(err) 46 + } 47 + model := model.Init(store, logger, xrpc) 34 48 httpclient := &http.Client{ 35 49 Timeout: 5 * time.Second, 36 50 Transport: &http.Transport{ ··· 42 56 logger.Println(err.Error()) 43 57 panic(err) 44 58 } 45 - h := handler.New(store, &logger, oauthclient) 46 - go consumeLoop(context.Background(), store, &logger) 59 + h := handler.New(store, logger, oauthclient, xrpc, model) 60 + go consumeLoop(context.Background(), store, logger) 47 61 http.ListenAndServe(":8080", h.WithCORSAll()) 48 62 49 63 }
+2 -2
server/go.mod
··· 11 11 github.com/jackc/pgx/v5 v5.7.4 12 12 github.com/joho/godotenv v1.5.1 13 13 github.com/lestrrat-go/jwx/v2 v2.0.12 14 - github.com/rachel-mp4/lrcd v0.0.0-20250603192958-089ba44e79a5 14 + github.com/rachel-mp4/lrcd v0.0.0-20250706133346-685719fb9bfc 15 + github.com/rachel-mp4/lrcproto v0.0.0-20250527205756-58da8216f98c 15 16 github.com/rivo/uniseg v0.4.7 16 17 github.com/whyrusleeping/cbor-gen v0.3.1 17 18 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 ··· 71 72 github.com/prometheus/client_model v0.6.1 // indirect 72 73 github.com/prometheus/common v0.54.0 // indirect 73 74 github.com/prometheus/procfs v0.15.1 // indirect 74 - github.com/rachel-mp4/lrcproto v0.0.0-20250527205756-58da8216f98c // indirect 75 75 github.com/segmentio/asm v1.2.0 // indirect 76 76 github.com/spaolacci/murmur3 v1.1.0 // indirect 77 77 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
+2 -2
server/go.sum
··· 161 161 github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 162 162 github.com/rachel-mp4/atproto-oauth-golang v0.0.0-20250616212213-a55a5f62b82d h1:FQ8YKfXnKmyEbKnO/blj3qWGhYdw+l3DtQCqSboJRvA= 163 163 github.com/rachel-mp4/atproto-oauth-golang v0.0.0-20250616212213-a55a5f62b82d/go.mod h1:vVRo6BPEmWOZnYk9LtXLzBPzfkY63fUaBahA+o4h55Q= 164 - github.com/rachel-mp4/lrcd v0.0.0-20250603192958-089ba44e79a5 h1:NMDkC4XYysiYebcoFDnsPdBVr8/NEuahKM6xqQJITp0= 165 - github.com/rachel-mp4/lrcd v0.0.0-20250603192958-089ba44e79a5/go.mod h1:Hn8xgJ2JwdiFJM5WjamVv4lRTwB6CdcqPjrCvJM7234= 164 + github.com/rachel-mp4/lrcd v0.0.0-20250706133346-685719fb9bfc h1:JbSq3FHQoEycu/R/ecJoXFeDRGqA2prfsk4cynSbDm0= 165 + github.com/rachel-mp4/lrcd v0.0.0-20250706133346-685719fb9bfc/go.mod h1:Hn8xgJ2JwdiFJM5WjamVv4lRTwB6CdcqPjrCvJM7234= 166 166 github.com/rachel-mp4/lrcproto v0.0.0-20250527205756-58da8216f98c h1:nOWeKeE7wph0IcwUyUBi0YBynUnAo4JW/J5DM88x4KM= 167 167 github.com/rachel-mp4/lrcproto v0.0.0-20250527205756-58da8216f98c/go.mod h1:hQzO36tQELGbkmRnUtKeM6NMU34t79ZcTlhM+MO7pHw= 168 168 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+5 -5
server/internal/atputils/identity.go
··· 27 27 return *my_handle 28 28 } 29 29 30 - func GetMyDid(ctx context.Context) (string, error) { 30 + func GetMyDid() string { 31 31 if my_did != nil { 32 - return *my_did, nil 32 + return *my_did 33 33 } 34 34 if my_handle == nil { 35 35 GetMyHandle() 36 36 } 37 - did, err := GetDidFromHandle(ctx, *my_handle) 37 + did, err := GetDidFromHandle(context.Background(), *my_handle) 38 38 if err != nil { 39 - return "", err 39 + return "" 40 40 } 41 41 my_did = &did 42 - return did, nil 42 + return did 43 43 } 44 44 45 45 func GetHandleFromDid(ctx context.Context, did string) (string, error) {
+5
server/internal/db/db.go
··· 199 199 } 200 200 return chans, nil 201 201 } 202 + 203 + func (s *Store) DeleteChannel(uri string, ctx context.Context) error { 204 + _, err := s.pool.Exec(ctx, `DELETE FROM channels WHERE uri = $1`, uri) 205 + return err 206 + }
+48
server/internal/db/lexicon.go
··· 127 127 `, channel.URI, channel.CID, channel.DID, channel.Host, channel.Title, channel.Topic, channel.CreatedAt) 128 128 return err 129 129 } 130 + 131 + func (s *Store) StoreMessage(message types.Message, ctx context.Context) error { 132 + _, err := s.pool.Exec(ctx, ` 133 + INSERT INTO messages ( 134 + uri, 135 + cid, 136 + did, 137 + signet_uri, 138 + body, 139 + nick, 140 + color, 141 + posted_at 142 + ) VALUES ( 143 + $1, $2, $3, $4, $5, $6, $7, $8 144 + ) ON CONFLICT (uri) DO NOTHING 145 + `, message.URI, message.CID, message.DID, message.SignetURI, message.Body, message.Nick, message.Color, message.PostedAt) 146 + return err 147 + } 148 + 149 + func (s *Store) QuerySignet(channelUri string, id uint32, ctx context.Context) (string, error) { 150 + row := s.pool.QueryRow(ctx, `SELECT s.uri FROM signets s WHERE s.channel_uri = $1 AND s.message_id = $2`, channelUri, id) 151 + var signetUri string 152 + err := row.Scan(&signetUri) 153 + if err != nil { 154 + return "", errors.New("error scanning: " + err.Error()) 155 + } 156 + return signetUri, nil 157 + } 158 + 159 + func (s *Store) StoreSignet(signet types.Signet, ctx context.Context) error { 160 + _, err := s.pool.Exec(ctx, ` 161 + INSERT INTO signets ( 162 + uri, 163 + issuer_did, 164 + did, 165 + channel_uri, 166 + message_id, 167 + cid, 168 + started_at 169 + ) VALUES ( 170 + $1, $2, $3, $4, $5, $6, %7 171 + ) ON CONFLICT (uri) DO NOTHING 172 + `, signet.URI, signet.IssuerDID, signet.DID, signet.ChannelURI, signet.MessageID, signet.CID, signet.StartedAt) 173 + if err != nil { 174 + err = errors.New("SOMETHING BAD HAPPENED: " + err.Error()) 175 + } 176 + return err 177 + }
+5 -26
server/internal/handler/handler.go
··· 1 1 package handler 2 2 3 3 import ( 4 - "context" 5 4 "github.com/gorilla/sessions" 6 5 "net/http" 7 6 8 7 "os" 9 - "xcvr-backend/internal/atputils" 10 8 "xcvr-backend/internal/db" 11 - "xcvr-backend/internal/lex" 12 9 "xcvr-backend/internal/log" 10 + "xcvr-backend/internal/model" 13 11 "xcvr-backend/internal/oauth" 14 12 ) 15 13 ··· 21 19 oauth *oauth.Service 22 20 myClient *oauth.PasswordClient 23 21 clientmap *oauth.ClientMap 22 + model *model.Model 24 23 } 25 24 26 - func New(db *db.Store, logger *log.Logger, oauthserv *oauth.Service) *Handler { 25 + func New(db *db.Store, logger *log.Logger, oauthserv *oauth.Service, xrpc *oauth.PasswordClient, model *model.Model) *Handler { 27 26 mux := http.NewServeMux() 28 27 sessionStore := sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) 29 - host, err := atputils.GetPDSFromHandle(context.Background(), atputils.GetMyHandle()) 30 - if err != nil { 31 - panic(err) 32 - } 33 - did, err := atputils.GetMyDid(context.Background()) 34 - if err != nil { 35 - panic(err) 36 - } 37 - xrpc := oauth.NewPasswordClient(did, host, logger) 38 - err = xrpc.CreateSession(context.Background()) 39 - if err != nil { 40 - panic(err) 41 - } 42 - _, _, err = xrpc.CreateXCVRSignet(&lex.SignetRecord{ 43 - ChannelURI: "beep.boop", 44 - LRCID: 11, 45 - Author: "sneep.snirp", 46 - }, context.Background()) 47 - if err != nil { 48 - panic(err) 49 - } 50 28 clientmap := oauth.NewClientMap() 51 - h := &Handler{db, sessionStore, mux, logger, oauthserv, xrpc, clientmap} 29 + h := &Handler{db, sessionStore, mux, logger, oauthserv, xrpc, clientmap, model} 52 30 // lrc handlers 53 31 mux.HandleFunc("GET /lrc/{user}/{rkey}/ws", h.acceptWebsocket) 32 + mux.HandleFunc("DELETE /lrc/{user}/{rkey}/ws", h.deleteChannel) 54 33 mux.HandleFunc("POST /lrc/channel", h.postChannel) 55 34 mux.HandleFunc("POST /lrc/message", h.postMessage) 56 35 // beep handlers
+109 -8
server/internal/handler/lrcHandlers.go
··· 6 6 "fmt" 7 7 "github.com/bluesky-social/indigo/atproto/syntax" 8 8 "net/http" 9 + "os" 9 10 "time" 10 11 "xcvr-backend/internal/atputils" 11 12 "xcvr-backend/internal/lex" 12 - "xcvr-backend/internal/model" 13 13 "xcvr-backend/internal/types" 14 14 ) 15 15 ··· 17 17 rkey := r.PathValue("rkey") 18 18 user := r.PathValue("user") 19 19 uri := fmt.Sprintf("at://%s/org.xcvr.feed.channel/%s", user, rkey) 20 - f, err := model.GetWSHandlerFrom(uri) 20 + f, err := h.model.GetWSHandlerFrom(uri) 21 21 if err != nil { 22 22 http.NotFound(w, r) 23 23 h.logger.Deprintf("couldn't find user %s's server %s", user, rkey) ··· 109 109 h.serverError(w, err) 110 110 return 111 111 } 112 - mydid, err := atputils.GetMyDid(r.Context()) 113 - if err != nil { 114 - h.serverError(w, err) 115 - return 116 - } 117 112 channel := types.Channel{ 118 113 URI: uri, 119 114 CID: cid, 120 - DID: mydid, 115 + DID: atputils.GetMyDid(), 121 116 Host: lcr.Host, 122 117 Title: lcr.Title, 123 118 Topic: lcr.Topic, ··· 133 128 134 129 } 135 130 131 + func (h *Handler) parseMessageRequest(r *http.Request) (*lex.MessageRecord, *time.Time, error) { 132 + var mr types.PostMessageRequest 133 + decoder := json.NewDecoder(r.Body) 134 + err := decoder.Decode(&mr) 135 + if err != nil { 136 + return nil, nil, errors.New("couldn't decode: " + err.Error()) 137 + } 138 + if mr.SignetURI == nil { 139 + if mr.MessageID == nil || mr.ChannelURI == nil { 140 + return nil, nil, errors.New("must provide a way to determine signet") 141 + } 142 + signetUri, err := h.db.QuerySignet(*mr.ChannelURI, *mr.MessageID, r.Context()) 143 + if err != nil { 144 + return nil, nil, errors.New("i couldn't find the signet :c : " + err.Error()) 145 + } 146 + mr.SignetURI = &signetUri 147 + } 148 + var lmr lex.MessageRecord 149 + lmr.SignetURI = *mr.SignetURI 150 + lmr.Body = mr.Body 151 + if mr.Nick != nil { 152 + nick := *mr.Nick 153 + if atputils.ValidateLength(nick, 16) { 154 + return nil, nil, errors.New("that nick is too long") 155 + } 156 + } 157 + lmr.Nick = mr.Nick 158 + 159 + if mr.Color != nil { 160 + color := uint64(*mr.Color) 161 + if color > 16777215 { 162 + return nil, nil, errors.New("that color is too big") 163 + } 164 + } 165 + now := syntax.DatetimeNow() 166 + lmr.PostedAt = now.String() 167 + nt := now.Time() 168 + return &lmr, &nt, nil 169 + } 170 + 171 + func (h *Handler) postMyMessage(w http.ResponseWriter, r *http.Request) { 172 + 173 + } 174 + 136 175 func (h *Handler) postMessage(w http.ResponseWriter, r *http.Request) { 176 + session, _ := h.sessionStore.Get(r, "oauthsession") 177 + _, ok := session.Values["id"].(uint) 178 + if !ok { 179 + h.postMyMessage(w, r) 180 + return 181 + } 182 + client, err := h.getClient(r) 183 + if err != nil { 184 + h.serverError(w, errors.New("couldn't find client: "+err.Error())) 185 + return 186 + } 137 187 188 + lmr, now, err := h.parseMessageRequest(r) 189 + if err != nil { 190 + h.badRequest(w, errors.New("couldn't parse message "+err.Error())) 191 + return 192 + } 193 + 194 + uri, cid, err := client.CreateXCVRMessage(*lmr, r.Context()) 195 + if err != nil { 196 + h.serverError(w, errors.New("couldn't add to user repo: ")) 197 + return 198 + } 199 + did := session.Values["did"].(string) 200 + var coloruint32ptr *uint32 201 + if lmr.Color != nil { 202 + color := uint32(*lmr.Color) 203 + coloruint32ptr = &color 204 + } 205 + message := types.Message{ 206 + URI: uri, 207 + DID: did, 208 + CID: cid, 209 + SignetURI: lmr.SignetURI, 210 + Body: lmr.Body, 211 + Nick: lmr.Nick, 212 + Color: coloruint32ptr, 213 + PostedAt: *now, 214 + } 215 + err = h.db.StoreMessage(message, r.Context()) 216 + if err != nil { 217 + h.serverError(w, errors.New("sooo... the record posted but i couldn't store it: "+err.Error())) 218 + return 219 + } 220 + h.getMessages(w, r) 221 + } 222 + 223 + func (h *Handler) deleteChannel(w http.ResponseWriter, r *http.Request) { 224 + did, handle, err := h.findDidAndHandle(r) 225 + if err != nil { 226 + return 227 + } 228 + rkey := r.PathValue("rkey") 229 + user := r.PathValue("user") 230 + if did != user && handle != os.Getenv("ADMIN_HANDLE") { 231 + return 232 + } 233 + uri := fmt.Sprintf("at://%s/org.xcvr.feed.channel/%s", user, rkey) 234 + err = h.db.DeleteChannel(uri, r.Context()) 235 + if err != nil { 236 + return 237 + } 238 + h.getChannels(w, r) 138 239 }
+2 -3
server/internal/log/log.go
··· 13 13 Slog *slog.Logger 14 14 } 15 15 16 - func New(w io.Writer, verbose bool) Logger { 16 + func New(w io.Writer, verbose bool) *Logger { 17 17 l := Logger{} 18 18 l.prodLogger = log.New(w, "[log]", log.Ldate|log.Ltime) 19 19 if verbose { 20 20 l.debugLogger = log.New(w, "[debug]", log.Ldate|log.Ltime) 21 21 } 22 22 l.Slog = slog.New(slog.NewTextHandler(w, nil)) 23 - return l 23 + return &l 24 24 } 25 25 26 26 func (l *Logger) Deprintln(s string) { ··· 42 42 func (l *Logger) Printf(format string, args ...any) { 43 43 l.Println(fmt.Sprintf(format, args...)) 44 44 } 45 -
+143 -16
server/internal/model/channel.go
··· 3 3 import ( 4 4 "context" 5 5 "errors" 6 + "github.com/bluesky-social/indigo/atproto/syntax" 6 7 "net/http" 7 8 "os" 9 + "sync" 10 + "time" 11 + "xcvr-backend/internal/atputils" 8 12 "xcvr-backend/internal/db" 13 + "xcvr-backend/internal/lex" 14 + "xcvr-backend/internal/log" 15 + "xcvr-backend/internal/oauth" 16 + "xcvr-backend/internal/types" 9 17 10 18 "github.com/rachel-mp4/lrcd" 19 + lrcpb "github.com/rachel-mp4/lrcproto/gen/go" 11 20 ) 12 21 13 - var ( 14 - validServer map[string]bool 15 - uriToServer = make(map[string]*lrcd.Server) 16 - ) 22 + type Model struct { 23 + store *db.Store 24 + uriMap map[string]*serverModel 25 + logger *log.Logger 26 + cli *oauth.PasswordClient 27 + mu sync.Mutex 28 + } 17 29 18 - func GetWSHandlerFrom(uri string) (http.HandlerFunc, error) { 19 - server, err := getServer(uri) 30 + type serverModel struct { 31 + valid bool 32 + server *lrcd.Server 33 + lastID uint32 34 + initChan chan lrcpb.Event_Init 35 + cancelFunc func() 36 + } 37 + 38 + func (m *Model) GetWSHandlerFrom(uri string) (http.HandlerFunc, error) { 39 + server, err := m.getServer(uri) 20 40 if err != nil { 21 41 return nil, err 22 42 } 23 43 return server.WSHandler(), nil 24 44 } 25 45 26 - func Init(store *db.Store) { 46 + func Init(store *db.Store, logger *log.Logger, cli *oauth.PasswordClient) *Model { 27 47 uris, err := store.GetChannelURIs(context.Background()) 28 48 if err != nil { 29 49 panic(err) 30 50 } 31 - validServer = make(map[string]bool, len(uris)) 51 + uriToServerModel := make(map[string]*serverModel, len(uris)) 32 52 myid := os.Getenv("MY_IDENTITY") 33 53 for _, uri := range uris { 34 - validServer[uri.URI] = (uri.Host == myid) 54 + valid := (uri.Host == myid) 55 + beep := serverModel{valid: valid} 56 + uriToServerModel[uri.URI] = &beep 57 + } 58 + return &Model{ 59 + store, 60 + uriToServerModel, 61 + logger, 62 + cli, 63 + sync.Mutex{}, 35 64 } 36 65 } 37 66 38 - func getServer(uri string) (*lrcd.Server, error) { 39 - if !validServer[uri] { 67 + func (m *Model) getServer(uri string) (*lrcd.Server, error) { 68 + m.mu.Lock() 69 + defer m.mu.Unlock() 70 + 71 + sm := m.uriMap[uri] 72 + if sm == nil { 40 73 return nil, errors.New("Not a valid server") 41 74 } 42 - server, ok := uriToServer[uri] 43 - if !ok { 75 + if !sm.valid { 76 + return nil, errors.New("Not hosted on this backend!") 77 + } 78 + 79 + if sm.server == nil { 44 80 var err error 45 - server, err = lrcd.NewServer(lrcd.WithLogging(os.Stdout,true)) 81 + lastID := sm.lastID 82 + initChan := make(chan lrcpb.Event_Init, 100) 83 + 84 + server, err := lrcd.NewServer( 85 + lrcd.WithLogging(os.Stdout, true), 86 + lrcd.WithInitialID(lastID), 87 + lrcd.WithInitChannel(initChan), 88 + ) 46 89 if err != nil { 47 90 return nil, errors.New("Error creating server") 48 91 } 49 - uriToServer[uri] = server 92 + 50 93 err = server.Start() 51 94 if err != nil { 52 95 return nil, errors.New("Error starting server") 53 96 } 97 + 98 + if sm.cancelFunc != nil { 99 + sm.cancelFunc() 100 + } 101 + 102 + ctx, cancel := context.WithCancel(context.Background()) 103 + sm.server = server 104 + sm.initChan = initChan 105 + sm.cancelFunc = cancel 106 + 107 + go m.handleInitEvents(ctx, uri, initChan) 54 108 } 55 - return server, nil 109 + return sm.server, nil 56 110 } 57 111 112 + func (m *Model) handleInitEvents(ctx context.Context, uri string, initChan <-chan lrcpb.Event_Init) { 113 + ticker := time.NewTicker(5 * time.Minute) 114 + defer ticker.Stop() 115 + 116 + for { 117 + select { 118 + case <-ctx.Done(): 119 + return 120 + case <-ticker.C: 121 + m.mu.Lock() 122 + sm := m.uriMap[uri] 123 + if sm == nil || sm.server == nil { 124 + m.mu.Unlock() 125 + return 126 + } 127 + 128 + c := sm.server.Connected() 129 + if c == 0 { 130 + lastID, err := sm.server.Stop() 131 + if err != nil { 132 + m.mu.Unlock() 133 + panic(err) 134 + } 135 + sm.lastID = lastID 136 + sm.server = nil 137 + sm.initChan = nil 138 + m.mu.Unlock() 139 + return 140 + } 141 + m.mu.Unlock() 142 + case e, ok := <-initChan: 143 + if !ok { 144 + return 145 + } 146 + signet := lex.SignetRecord{} 147 + handle := e.Init.ExternalID 148 + if handle == nil { 149 + h := "" 150 + handle = &h 151 + } 152 + signet.Author = *handle 153 + if e.Init.Id == nil { 154 + m.logger.Deprintln("initchannel gave me a nil id") 155 + continue 156 + } 157 + lrcid := uint64(*e.Init.Id) 158 + signet.LRCID = lrcid 159 + signet.ChannelURI = uri 160 + now := syntax.DatetimeNow() 161 + nowTime := now.Time() 162 + nowString := now.String() 163 + signet.StartedAt = &nowString 164 + cid, recorduri, err := m.cli.CreateXCVRSignet(&signet, context.Background()) 165 + if err != nil { 166 + m.logger.Deprintf("couldn't post a signet in %s: %s", uri, err.Error()) 167 + continue 168 + } 169 + sr := types.Signet{ 170 + URI: recorduri, 171 + IssuerDID: atputils.GetMyDid(), 172 + DID: signet.Author, 173 + ChannelURI: uri, 174 + MessageID: *e.Init.Id, 175 + CID: cid, 176 + StartedAt: nowTime, 177 + } 178 + err = m.store.StoreSignet(sr, context.Background()) 179 + if err != nil { 180 + m.logger.Println("failed to store signet!") 181 + } 182 + } 183 + } 184 + }
+8 -4
server/internal/oauth/oauthclient.go
··· 127 127 return 128 128 } 129 129 130 - func (c *OauthXRPCClient) CreateXCVRMessage(message lex.MessageRecord, ctx context.Context) error { 130 + func (c *OauthXRPCClient) CreateXCVRMessage(message lex.MessageRecord, ctx context.Context) (uri string, cid string, err error) { 131 131 authargs, err := c.getOauthSessionAuthArgs() 132 132 if err != nil { 133 - return errors.New("uh oh... I couldn't make a XCVRMessage: " + err.Error()) 133 + err = errors.New("uh oh... I couldn't make a XCVRMessage: " + err.Error()) 134 + return 134 135 } 135 136 input := atproto.RepoCreateRecord_Input{ 136 137 Collection: "org.xcvr.lrc.message", ··· 140 141 var out atproto.RepoCreateRecord_Output 141 142 err = c.xrpc.Do(ctx, authargs, "POST", "application/json", "com.atproto.repo.createRecord", nil, input, &out) 142 143 if err != nil { 143 - return errors.New("i've got a bad feeling aobut this... failed to create XCVRMessage: " + err.Error()) 144 + err = errors.New("i've got a bad feeling aobut this... failed to create XCVRMessage: " + err.Error()) 145 + return 144 146 } 145 - return nil 147 + uri = out.Uri 148 + cid = out.Cid 149 + return 146 150 } 147 151 148 152 func (c *OauthXRPCClient) UpdateXCVRProfile(profile lex.ProfileRecord, ctx context.Context) error {
+12 -2
server/internal/types/lexicons.go
··· 81 81 82 82 type Signet struct { 83 83 URI string 84 + IssuerDID string 84 85 DID string 85 86 ChannelURI string 86 87 MessageID uint32 ··· 94 95 DID string 95 96 SignetURI string 96 97 Body string 97 - Nick string 98 - Color uint32 98 + Nick *string 99 + Color *uint32 99 100 CID string 100 101 PostedAt time.Time 101 102 IndexedAt time.Time 103 + } 104 + 105 + type PostMessageRequest struct { 106 + SignetURI *string `json:"signetURI,omitempty"` 107 + ChannelURI *string `json:"channelURI,omitempty"` 108 + MessageID *uint32 `json:"messageID,omitempty"` 109 + Body string `json:"body"` 110 + Nick *string `json:"nick,omitempty"` 111 + Color *uint32 `json:"color,omitempty"` 102 112 } 103 113 104 114 type MessageView struct {