tiny 88x31 lexicon for atproto
0
fork

Configure Feed

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

some things

+124 -9
+60 -4
db/lexicon.go
··· 8 8 "time" 9 9 ) 10 10 11 + func (s *Store) GetButtonsAuth(limit int, cursor *string, ctx context.Context, did string) ([]types.ButtonViewAuth, time.Time, error) { 12 + query := ` 13 + SELECT 14 + b.uri, 15 + b.did, 16 + b.alt, 17 + b.href, 18 + b.posted_at, 19 + b.like_count, 20 + FROM buttons b 21 + ORDER BY posted_at DESC 22 + %s 23 + LIMIT $1 24 + ` 25 + if cursor != nil { 26 + query = fmt.Sprintf(query, "WHERE posted_at < $2") 27 + } else { 28 + query = fmt.Sprintf(query, "") 29 + } 30 + rows, err := s.pool.Query(ctx, query, limit, cursor) 31 + if err != nil { 32 + if err == pgx.ErrNoRows { 33 + return nil, time.Time{}, nil 34 + } 35 + return nil, time.Time{}, err 36 + } 37 + defer rows.Close() 38 + var buttons = make([]types.ButtonViewAuth, 0) 39 + var postedAt time.Time 40 + for rows.Next() { 41 + var btnView types.ButtonViewAuth 42 + err := rows.Scan(&btnView.Src, &btnView.DID, &btnView.Alt, &btnView.HREF, &postedAt, &btnView.Likes, &btnView.Liked) 43 + if err != nil { 44 + return nil, time.Time{}, err 45 + } 46 + btnView.Src = fmt.Sprintf("/xrpc/store.88x31.getButton?uri=%s", btnView.Src) 47 + buttons = append(buttons, btnView) 48 + } 49 + return buttons, postedAt, nil 50 + } 51 + 11 52 func (s *Store) GetButtons(limit int, cursor *string, ctx context.Context) ([]types.ButtonView, time.Time, error) { 12 53 query := ` 13 54 SELECT ··· 15 56 did, 16 57 alt, 17 58 href, 18 - posted_at 59 + posted_at, 60 + like_count 19 61 FROM buttons 20 62 ORDER BY posted_at DESC 21 63 %s ··· 38 80 var postedAt time.Time 39 81 for rows.Next() { 40 82 var btnView types.ButtonView 41 - err := rows.Scan(&btnView.Src, &btnView.DID, &btnView.Alt, &btnView.HREF, &postedAt) 83 + err := rows.Scan(&btnView.Src, &btnView.DID, &btnView.Alt, &btnView.HREF, &postedAt, &btnView.Likes) 42 84 if err != nil { 43 85 return nil, time.Time{}, err 44 86 } ··· 46 88 buttons = append(buttons, btnView) 47 89 } 48 90 return buttons, postedAt, nil 49 - 50 91 } 51 92 52 93 func (s *Store) GetButton(uri string, ctx context.Context) (*types.Button, error) { ··· 90 131 ) VALUES ( 91 132 $1, $2, $3, $4, $5, $6, $7, $8 92 133 ) 93 - WHERE uri = $1 94 134 `, tbr.URI, tbr.DID, tbr.BlobCID, tbr.BlobMIME, tbr.Alt, tbr.HREF, tbr.CID, tbr.PostedAt) 95 135 return err 96 136 } 137 + 138 + func (s *Store) StoreLike(tlr *types.Like, ctx context.Context) error { 139 + _, err := s.pool.Exec(ctx, ` 140 + INSERT INTO likes ( 141 + uri, 142 + subject_uri, 143 + subject_cid, 144 + did, 145 + cid, 146 + created_at 147 + ) VALUES ( 148 + $1, $2, $3, $4, $5, $6 149 + ) 150 + `, tlr.URI, tlr.SubjectURI, tlr.SubjectCID, tlr.DID, tlr.CID, tlr.CreatedAt) 151 + return err 152 + }
+1
handler/handler.go
··· 40 40 mux.HandleFunc("GET /xrpc/store.88x31.getButtons", h.WithCORS(h.getButtons)) 41 41 mux.HandleFunc(oauthCallbackPath(), h.oauthCallback) 42 42 mux.HandleFunc(clientMetadataPath(), h.WithCORS(h.serveClientMetadata)) 43 + mux.HandleFunc(oauthJWKSPath(), h.WithCORS(h.serveJWKS)) 43 44 return h 44 45 } 45 46
+33
handler/oauthhandlers.go
··· 13 13 myoauth "tangled.org/moth11.net/88x31/oauth" 14 14 ) 15 15 16 + func (h *Handler) serveJWKS(w http.ResponseWriter, r *http.Request) { 17 + key, err := myoauth.GetPrivateKey() 18 + if err != nil { 19 + http.Error(w, "couldn't get key", http.StatusInternalServerError) 20 + return 21 + } 22 + pubKey, err := key.PublicKey() 23 + if err != nil { 24 + http.Error(w, "couldn't get pubkey", http.StatusInternalServerError) 25 + return 26 + } 27 + ro, err := pubKey.JWK() 28 + if err != nil { 29 + http.Error(w, "couldn't get jwk", http.StatusInternalServerError) 30 + return 31 + } 32 + 33 + cski := os.Getenv("CLIENT_SECRET_KEY_ID") 34 + ro.KeyID = &cski 35 + rro := map[string]any{"keys": []any{ro}} 36 + w.Header().Set("Content-Type", "application/json") 37 + encoder := json.NewEncoder(w) 38 + err = encoder.Encode(rro) 39 + if err != nil { 40 + http.Error(w, "couldn't encode jwks", http.StatusInternalServerError) 41 + } 42 + } 43 + 16 44 func (h *Handler) logout(cs *oauth.ClientSession, w http.ResponseWriter, r *http.Request) { 17 45 if cs != nil { 18 46 err := h.db.DeleteSession(r.Context(), cs.Data.AccountDID, cs.Data.SessionID) ··· 55 83 56 84 func clientMetadataPath() string { 57 85 mp := os.Getenv("MY_METADATA_PATH") 86 + return fmt.Sprintf("GET %s", mp) 87 + } 88 + 89 + func oauthJWKSPath() string { 90 + mp := os.Getenv("MY_JWKS_PATH") 58 91 return fmt.Sprintf("GET %s", mp) 59 92 } 60 93
+2 -1
migrations/002_buttons.up.sql
··· 7 7 href TEXT, 8 8 cid TEXT NOT NULL, 9 9 posted_at TIMESTAMPTZ NOT NULL DEFAULT now(), 10 - indexed_at TIMESTAMPTZ NOT NULL DEFAULT now() 10 + indexed_at TIMESTAMPTZ NOT NULL DEFAULT now(), 11 + like_count INTEGER NOT NULL DEFAULT 0 11 12 ); 12 13 13 14 CREATE INDEX ON buttons (posted_at);
+2
migrations/005_likeindices.down.sql
··· 1 + DELETE INDEX IF EXISTS likes_did_subject_uri_idx; 2 + DELETE INDEX IF EXISTS likes_subject_uri_idx;
+2
migrations/005_likeindices.up.sql
··· 1 + CREATE INDEX ON likes (subject_uri); 2 + CREATE INDEX ON likes (did, subject_uri);
+24 -4
types/lexicon.go
··· 17 17 } 18 18 19 19 type ButtonView struct { 20 - DID string `json:"did"` 21 - Src string `json:"src"` 22 - Alt *string `json:"alt,omitempty"` 23 - HREF *string `json:"href,omitempty"` 20 + DID string `json:"did"` 21 + Src string `json:"src"` 22 + Alt *string `json:"alt,omitempty"` 23 + HREF *string `json:"href,omitempty"` 24 + Likes int `json:"likes"` 25 + } 26 + 27 + type ButtonViewAuth struct { 28 + DID string `json:"did"` 29 + Src string `json:"src"` 30 + Alt *string `json:"alt,omitempty"` 31 + HREF *string `json:"href,omitempty"` 32 + Likes int `json:"likes"` 33 + Liked bool `json:"liked"` 34 + } 35 + 36 + type Like struct { 37 + URI string 38 + SubjectURI string 39 + SubjectCID string 40 + DID string 41 + CID string 42 + CreatedAt time.Time 43 + IndexedAt time.Time 24 44 }