tiny 88x31 lexicon for atproto

add listener

+217 -32
+170
atplistener/jetstream.go
··· 1 + package atplistener 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "errors" 7 + "fmt" 8 + "github.com/bluesky-social/jetstream/pkg/client" 9 + "github.com/bluesky-social/jetstream/pkg/client/schedulers/sequential" 10 + "github.com/bluesky-social/jetstream/pkg/models" 11 + "log/slog" 12 + "tangled.org/moth11.net/88x31/db" 13 + "tangled.org/moth11.net/88x31/lex" 14 + "tangled.org/moth11.net/88x31/types" 15 + "time" 16 + ) 17 + 18 + type Consumer struct { 19 + cfg *client.ClientConfig 20 + logger *slog.Logger 21 + handler *handler 22 + } 23 + 24 + type handler struct { 25 + db *db.Store 26 + l *slog.Logger 27 + } 28 + 29 + func NewConsumer(jsAddr string, l *slog.Logger, db *db.Store) *Consumer { 30 + cfg := client.DefaultClientConfig() 31 + if jsAddr != "" { 32 + cfg.WebsocketURL = jsAddr 33 + } 34 + cfg.WantedCollections = []string{ 35 + "store.88x31.button", 36 + "store.88x31.like", 37 + } 38 + cfg.WantedDids = []string{} 39 + return &Consumer{ 40 + cfg: cfg, 41 + logger: l, 42 + handler: &handler{db: db, l: l}, 43 + } 44 + } 45 + 46 + func (c *Consumer) Consume(ctx context.Context) error { 47 + scheduler := sequential.NewScheduler("jetstream_localdev", c.logger, c.handler.HandleEvent) 48 + defer scheduler.Shutdown() 49 + client, err := client.NewClient(c.cfg, c.logger, scheduler) 50 + if err != nil { 51 + return errors.New("failed to create client: " + err.Error()) 52 + } 53 + cursor := time.Now().Add(1 * -time.Minute).UnixMicro() 54 + err = client.ConnectAndRead(ctx, &cursor) 55 + if err != nil { 56 + return errors.New("error connecting and reading: " + err.Error()) 57 + } 58 + return nil 59 + } 60 + 61 + func (h *handler) HandleEvent(ctx context.Context, event *models.Event) error { 62 + if event.Commit == nil { 63 + return nil 64 + } 65 + switch event.Commit.Collection { 66 + case "store.88x31.button": 67 + return h.handleButton(ctx, event) 68 + case "store.88x31.like": 69 + return h.handleLike(ctx, event) 70 + } 71 + return nil 72 + } 73 + 74 + func (h *handler) handleButton(ctx context.Context, event *models.Event) error { 75 + h.l.Debug("handling button") 76 + switch event.Commit.Operation { 77 + case "create", "update": 78 + return h.handleButtonCreateUpdate(ctx, event) 79 + case "delete": 80 + return h.handleButtonDelete(ctx, event) 81 + } 82 + return errors.New("unsupported commit operation") 83 + } 84 + 85 + func (h *handler) handleButtonCreateUpdate(ctx context.Context, event *models.Event) error { 86 + var br lex.ButtonRecord 87 + err := json.Unmarshal(event.Commit.Record, &br) 88 + if err != nil { 89 + h.l.Error("error unmarshaling: " + err.Error()) 90 + return nil 91 + } 92 + pa, err := time.Parse(br.PostedAt, time.RFC3339) 93 + if err != nil { 94 + h.l.Debug("bad time: " + err.Error()) 95 + pa = time.Time{} 96 + } 97 + tbr := types.Button{URI: uri(event), 98 + DID: event.Did, 99 + BlobCID: br.Blob.Ref.String(), 100 + BlobMIME: br.Blob.MimeType, 101 + Alt: br.Alt, 102 + Title: br.Title, 103 + HREF: br.Href, 104 + CID: event.Commit.CID, 105 + PostedAt: pa, 106 + } 107 + err = h.db.StoreButton(&tbr, ctx) 108 + if err != nil { 109 + return err 110 + } 111 + return nil 112 + } 113 + 114 + func (h *handler) handleButtonDelete(ctx context.Context, event *models.Event) error { 115 + 116 + err := h.db.DeleteButton(uri(event), ctx) 117 + if err != nil { 118 + h.l.Error(err.Error()) 119 + } 120 + return nil 121 + } 122 + 123 + func uri(event *models.Event) string { 124 + return fmt.Sprintf("at://%s/%s/%s", event.Did, event.Commit.Collection, event.Commit.RKey) 125 + } 126 + func (h *handler) handleLike(ctx context.Context, event *models.Event) error { 127 + h.l.Debug("handling like") 128 + switch event.Commit.Operation { 129 + case "create", "update": 130 + return h.handleLikeCreateUpdate(ctx, event) 131 + case "delete": 132 + return h.handleLikeDelete(ctx, event) 133 + } 134 + return errors.New("unsupported commit operation") 135 + } 136 + 137 + func (h *handler) handleLikeCreateUpdate(ctx context.Context, event *models.Event) error { 138 + var lr lex.LikeRecord 139 + err := json.Unmarshal(event.Commit.Record, &lr) 140 + if err != nil { 141 + h.l.Error("error unmarshaling: " + err.Error()) 142 + return nil 143 + } 144 + pa, err := time.Parse(lr.CreatedAt, time.RFC3339) 145 + if err != nil { 146 + h.l.Debug("bad time: " + err.Error()) 147 + pa = time.Time{} 148 + } 149 + tlr := types.Like{ 150 + URI: uri(event), 151 + SubjectURI: lr.Subject.URI, 152 + SubjectCID: lr.Subject.CID, 153 + DID: event.Did, 154 + CID: event.Commit.CID, 155 + CreatedAt: pa, 156 + } 157 + err = h.db.StoreLike(&tlr, ctx) 158 + if err != nil { 159 + return err 160 + } 161 + return nil 162 + } 163 + 164 + func (h *handler) handleLikeDelete(ctx context.Context, event *models.Event) error { 165 + err := h.db.DeleteLike(uri(event), ctx) 166 + if err != nil { 167 + h.l.Error(err.Error()) 168 + } 169 + return nil 170 + }
+1 -1
db/lexicon.go
··· 260 260 posted_at 261 261 ) VALUES ( 262 262 $1, $2, $3, $4, $5, $6, $7, $8, $9 263 - ) 263 + ) ON CONFLICT (uri) DO UPDATE 264 264 `, tbr.URI, tbr.DID, tbr.BlobCID, tbr.BlobMIME, tbr.Alt, tbr.Title, tbr.HREF, tbr.CID, tbr.PostedAt) 265 265 return err 266 266 }
+14 -9
go.mod
··· 3 3 go 1.25 4 4 5 5 require ( 6 - github.com/bluesky-social/indigo v0.0.0-20251127021457-6f2658724b36 6 + github.com/bluesky-social/indigo v0.0.0-20260120225912-12d69fa4d209 7 + github.com/bluesky-social/jetstream v0.0.0-20260121001058-f4e39a4b5bbc 7 8 github.com/gorilla/sessions v1.4.0 8 9 github.com/ipfs/go-cid v0.4.1 9 10 github.com/jackc/pgx/v5 v5.7.6 ··· 14 15 15 16 require ( 16 17 github.com/beorn7/perks v1.0.1 // indirect 17 - github.com/cespare/xxhash/v2 v2.2.0 // indirect 18 + github.com/cespare/xxhash/v2 v2.3.0 // indirect 18 19 github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect 20 + github.com/goccy/go-json v0.10.2 // indirect 19 21 github.com/golang-jwt/jwt/v5 v5.2.2 // indirect 20 22 github.com/google/go-querystring v1.1.0 // indirect 21 23 github.com/gorilla/securecookie v1.1.2 // indirect 24 + github.com/gorilla/websocket v1.5.1 // indirect 22 25 github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 23 26 github.com/jackc/pgpassfile v1.0.0 // indirect 24 27 github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect 25 28 github.com/jackc/puddle/v2 v2.2.2 // indirect 29 + github.com/klauspost/compress v1.17.9 // indirect 26 30 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 27 - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 28 31 github.com/minio/sha256-simd v1.0.1 // indirect 29 32 github.com/mr-tron/base58 v1.2.0 // indirect 30 33 github.com/multiformats/go-base32 v0.1.0 // indirect ··· 32 35 github.com/multiformats/go-multibase v0.2.0 // indirect 33 36 github.com/multiformats/go-multihash v0.2.3 // indirect 34 37 github.com/multiformats/go-varint v0.0.7 // indirect 35 - github.com/prometheus/client_golang v1.17.0 // indirect 36 - github.com/prometheus/client_model v0.5.0 // indirect 37 - github.com/prometheus/common v0.45.0 // indirect 38 - github.com/prometheus/procfs v0.12.0 // indirect 38 + github.com/prometheus/client_golang v1.19.1 // indirect 39 + github.com/prometheus/client_model v0.6.1 // indirect 40 + github.com/prometheus/common v0.54.0 // indirect 41 + github.com/prometheus/procfs v0.15.1 // indirect 39 42 github.com/spaolacci/murmur3 v1.1.0 // indirect 40 43 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 41 44 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 45 + go.uber.org/atomic v1.11.0 // indirect 42 46 golang.org/x/crypto v0.37.0 // indirect 47 + golang.org/x/net v0.24.0 // indirect 43 48 golang.org/x/sync v0.13.0 // indirect 44 49 golang.org/x/sys v0.32.0 // indirect 45 50 golang.org/x/text v0.24.0 // indirect 46 - golang.org/x/time v0.3.0 // indirect 47 - google.golang.org/protobuf v1.33.0 // indirect 51 + golang.org/x/time v0.5.0 // indirect 52 + google.golang.org/protobuf v1.34.2 // indirect 48 53 lukechampine.com/blake3 v1.2.1 // indirect 49 54 )
+30 -20
go.sum
··· 1 1 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 2 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 - github.com/bluesky-social/indigo v0.0.0-20251127021457-6f2658724b36 h1:Vc+l4sltxQfBT8qC3dm87PRYInmxlGyF1dmpjaW0WkU= 4 - github.com/bluesky-social/indigo v0.0.0-20251127021457-6f2658724b36/go.mod h1:Pm2I1+iDXn/hLbF7XCg/DsZi6uDCiOo7hZGWprSM7k0= 5 - github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 6 - github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 3 + github.com/bluesky-social/indigo v0.0.0-20260120225912-12d69fa4d209 h1:W01PGqjCexVBzIZ4FoNe4iO8OhI9XbSE7ieWL0QnMu8= 4 + github.com/bluesky-social/indigo v0.0.0-20260120225912-12d69fa4d209/go.mod h1:KIy0FgNQacp4uv2Z7xhNkV3qZiUSGuRky97s7Pa4v+o= 5 + github.com/bluesky-social/jetstream v0.0.0-20260121001058-f4e39a4b5bbc h1:PhAg2bDYjOGxMTgBe4VgwrTyeEVRX+8R997TSoT15QM= 6 + github.com/bluesky-social/jetstream v0.0.0-20260121001058-f4e39a4b5bbc/go.mod h1:vt8kVRKtvrBspt9G38wDD8+BotjIMO8u8IYoVnyE4zY= 7 + github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 8 + github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 9 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 10 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 11 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 12 github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= 11 13 github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= 14 + github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 15 + github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 12 16 github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= 13 17 github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 14 18 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 15 - github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 16 - github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 19 + github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 20 + github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 17 21 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 18 22 github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 19 23 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= ··· 22 26 github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 23 27 github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= 24 28 github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 29 + github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 30 + github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 25 31 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 26 32 github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 27 33 github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= ··· 36 42 github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 37 43 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 38 44 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 45 + github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 46 + github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 39 47 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 40 48 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 41 - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= 42 - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 43 49 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 44 50 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 45 51 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= ··· 56 62 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 57 63 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 58 64 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 59 - github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= 60 - github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= 61 - github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= 62 - github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= 63 - github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= 64 - github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= 65 - github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= 66 - github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 65 + github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= 66 + github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 67 + github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 68 + github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 69 + github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= 70 + github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= 71 + github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 72 + github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 67 73 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 68 74 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 69 75 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= ··· 77 83 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 78 84 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 79 85 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 86 + go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 87 + go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 80 88 golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 81 89 golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 90 + golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 91 + golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 82 92 golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 83 93 golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 84 94 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ··· 86 96 golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 87 97 golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 88 98 golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 89 - golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 90 - golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 99 + golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 100 + golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 91 101 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 92 102 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 93 103 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 94 - google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 95 - google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 104 + google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 105 + google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 96 106 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 97 107 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 98 108 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+2 -2
tmpl/upload.html
··· 1 1 {{define "content"}} 2 2 <form action="/upload" method="POST" enctype="multipart/form-data"> 3 3 <div><input type="file" name="button" value="select file" accept="image/png, image/jpg, image/gif"/></div> 4 - <div><input type="text" name="alt" placeholder="alt text"/></div> 5 - <div><input type="text" name="title" placeholder="title text"/></div> 4 + <div><input type="text" name="alt" placeholder="alt text (accessibility)"/></div> 5 + <div><input type="text" name="title" placeholder="title text (hover)"/></div> 6 6 <div><input type="text" name="href" placeholder="link"/></div> 7 7 <div><input type="submit" value="upload"/></div> 8 8 </form>