Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ type Record struct {
Value []byte
}

type PrivateRecord struct {
Did string `gorm:"primaryKey:idx_private_record_did_created_at;index:idx_private_record_did_nsid"`
CreatedAt string `gorm:"index;index:idx_private_record_did_created_at,sort:desc"`
Nsid string `gorm:"primarKey;index:idx_private_record_did_nsid"`
Rkey string `gorm:"primaryKey"`
Cid string
Value []byte
}

type Block struct {
Did string `gorm:"primaryKey;index:idx_blocks_by_rev"`
Cid []byte `gorm:"primaryKey"`
Expand Down
57 changes: 57 additions & 0 deletions server/handle_repo_create_private.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package server

import (
"encoding/json"
"fmt"
"time"

"github.com/Azure/go-autorest/autorest/to"
"github.com/haileyok/cocoon/models"
"github.com/labstack/echo/v4"
)

type ComAtprotoUnspeccedCreatePrivateRecordInput struct {
Repo string `json:"repo" validate:"required,atproto-did"`
Collection string `json:"collection" validate:"required,atproto-nsid"`
Rkey *string `json:"rkey,omitempty"`
Validate *bool `json:"bool,omitempty"`
Record MarshalableMap `json:"record" validate:"required"`
}

func (s *Server) handleServerCreatePrivate(e echo.Context) error {
ctx := e.Request().Context()
logger := s.logger.With("name", "handleCreatePrivate")

repo := e.Get("repo").(*models.RepoActor)

var input ComAtprotoUnspeccedCreatePrivateRecordInput
if err := e.Bind(&input); err != nil {
logger.Error("error binding", "err", err)
return fmt.Errorf("error binding: %w", err)
}

if input.Rkey == nil {
input.Rkey = to.StringPtr(s.repoman.clock.Next().String())
}

b, err := json.Marshal(input.Record)
if err != nil {
logger.Error("failed to marshal input record", "err", err)
return fmt.Errorf("failed to marshal input record: %w", err)
}

record := models.PrivateRecord{
Did: repo.Repo.Did,
CreatedAt: time.Now().UTC().Format(time.RFC3339Nano),
Nsid: input.Collection,
Rkey: *input.Rkey,
Value: b,
}

if err := s.db.Create(ctx, &record, nil).Error; err != nil {
logger.Error("failed to create record in db", "err", err)
return fmt.Errorf("failed to create record in db")
}

return e.NoContent(200)
}
41 changes: 41 additions & 0 deletions server/handle_repo_get_private.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package server

import (
"encoding/json"
"fmt"

"github.com/haileyok/cocoon/models"
"github.com/labstack/echo/v4"
)

type ComAtprotoUnspeccedGetPrivateRecordInput struct {
Collection string `query:"collection"`
Rkey string `query:"rkey"`
}

func (s *Server) handleServerGetPrivate(e echo.Context) error {
ctx := e.Request().Context()
logger := s.logger.With("name", "handleGetPrivate")

repo := e.Get("repo").(*models.RepoActor)

var input ComAtprotoUnspeccedGetPrivateRecordInput
if err := e.Bind(&input); err != nil {
logger.Error("error binding", "err", err)
return fmt.Errorf("error binding: %w", err)
}

var record models.PrivateRecord
if err := s.db.Raw(ctx, "SELECT * FROM private_records WHERE did = ? AND nsid = ? AND rkey = ?", nil, repo.Repo.Did, input.Collection, input.Rkey).Scan(&record).Error; err != nil {
logger.Error("error getting private record", "err", err)
return fmt.Errorf("failed to get private record: %w", err)
}

var unmarshaled map[string]any
if err := json.Unmarshal(record.Value, &unmarshaled); err != nil {
logger.Error("error unmarshaling record", "err", err)
return fmt.Errorf("failed to unmarshal record: %w", err)
}

return e.JSON(200, unmarshaled)
}
81 changes: 81 additions & 0 deletions server/handle_repo_list_private.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package server

import (
"encoding/json"
"fmt"
"time"

"github.com/Azure/go-autorest/autorest/to"
"github.com/haileyok/cocoon/models"
"github.com/labstack/echo/v4"
)

type ComAtprotoUnspeccedListPrivateRecordsInput struct {
Collection string `query:"collection"`
Limit int `query:"limit"`
Cursor string `query:"cursor"`
}

type ComAtprotoUnspeccedListPrivateRecordsResponse struct {
Cursor *string `json:"cursor,omitempty"`
Records []ComAtprotoUnspeccedListPrivateRecordsRecordItem `json:"records"`
}

type ComAtprotoUnspeccedListPrivateRecordsRecordItem struct {
Uri string `json:"uri"`
Value map[string]any `json:"value"`
}

func (s *Server) handleServerListPrivate(e echo.Context) error {
ctx := e.Request().Context()
logger := s.logger.With("name", "handleListPrivate")

repo := e.Get("repo").(*models.RepoActor)

var input ComAtprotoUnspeccedListPrivateRecordsInput
if err := e.Bind(&input); err != nil {
logger.Error("error binding", "err", err)
return fmt.Errorf("error binding: %w", err)
}

limit, err := getLimitFromContext(e, 100)
if err != nil {
logger.Error("failed to parse limit from context", "err", err)
return fmt.Errorf("failed to parse limit from context: %w", err)
}

if input.Cursor == "" {
input.Cursor = time.Now().UTC().Format(time.RFC3339Nano)
}

var records []models.PrivateRecord
if err := s.db.Raw(ctx, "SELECT * FROM private_records WHERE did = ? AND nsid = ? AND created_at < ORDER BY created_at DESC LIMIT ?", nil, repo.Repo.Did, input.Collection, input.Cursor, limit).Scan(&records).Error; err != nil {
logger.Error("error getting private record", "err", err)
return fmt.Errorf("failed to get private record: %w", err)
}

respRecords := make([]ComAtprotoUnspeccedListPrivateRecordsRecordItem, 0, len(records))

for _, rec := range records {
var unmarshaled map[string]any
if err := json.Unmarshal(rec.Value, &unmarshaled); err != nil {
logger.Error("failed to unmarshal record", "err", err)
return fmt.Errorf("failed to unmarshal record: %w", err)
}

respRecords = append(respRecords, ComAtprotoUnspeccedListPrivateRecordsRecordItem{
Uri: fmt.Sprintf("at://%s/%s/%s", repo.Repo.Did, input.Collection, rec.Rkey),
Value: unmarshaled,
})
}

var newcursor *string
if len(records) == limit {
newcursor = to.StringPtr(records[len(records)-1].CreatedAt)
}

return e.JSON(200, ComAtprotoUnspeccedListPrivateRecordsResponse{
Cursor: newcursor,
Records: respRecords,
})
}
6 changes: 6 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,11 @@ func (s *Server) addRoutes() {
s.echo.POST("/xrpc/com.atproto.repo.uploadBlob", s.handleRepoUploadBlob, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
s.echo.POST("/xrpc/com.atproto.repo.importRepo", s.handleRepoImportRepo, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)

// temp private record routes
s.echo.POST("/xrpc/com.atproto.unspecced.createPrivateRecord", s.handleServerCreatePrivate, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
s.echo.GET("/xrpc/com.atproto.unspecced.getPrivateRecord", s.handleServerGetPrivate, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
s.echo.GET("/xrpc/com.atproto.unspecced.listPrivateRecords", s.handleServerListPrivate, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)

// stupid silly endpoints
s.echo.GET("/xrpc/app.bsky.actor.getPreferences", s.handleActorGetPreferences, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
s.echo.POST("/xrpc/app.bsky.actor.putPreferences", s.handleActorPutPreferences, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
Expand Down Expand Up @@ -560,6 +565,7 @@ func (s *Server) Serve(ctx context.Context) error {
&models.Blob{},
&models.BlobPart{},
&models.ReservedKey{},
&models.PrivateRecord{},
&provider.OauthToken{},
&provider.OauthAuthorizationRequest{},
)
Expand Down