+5
-1
.tangled/workflows/build.yml
+5
-1
.tangled/workflows/build.yml
+1
-1
.tangled/workflows/fmt.yml
+1
-1
.tangled/workflows/fmt.yml
+2
-2
.tangled/workflows/test.yml
+2
-2
.tangled/workflows/test.yml
+269
-297
api/tangled/cbor_gen.go
+269
-297
api/tangled/cbor_gen.go
···
504
504
505
505
return nil
506
506
}
507
+
func (t *FeedReaction) MarshalCBOR(w io.Writer) error {
508
+
if t == nil {
509
+
_, err := w.Write(cbg.CborNull)
510
+
return err
511
+
}
512
+
513
+
cw := cbg.NewCborWriter(w)
514
+
515
+
if _, err := cw.Write([]byte{164}); err != nil {
516
+
return err
517
+
}
518
+
519
+
// t.LexiconTypeID (string) (string)
520
+
if len("$type") > 1000000 {
521
+
return xerrors.Errorf("Value in field \"$type\" was too long")
522
+
}
523
+
524
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
525
+
return err
526
+
}
527
+
if _, err := cw.WriteString(string("$type")); err != nil {
528
+
return err
529
+
}
530
+
531
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.feed.reaction"))); err != nil {
532
+
return err
533
+
}
534
+
if _, err := cw.WriteString(string("sh.tangled.feed.reaction")); err != nil {
535
+
return err
536
+
}
537
+
538
+
// t.Subject (string) (string)
539
+
if len("subject") > 1000000 {
540
+
return xerrors.Errorf("Value in field \"subject\" was too long")
541
+
}
542
+
543
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil {
544
+
return err
545
+
}
546
+
if _, err := cw.WriteString(string("subject")); err != nil {
547
+
return err
548
+
}
549
+
550
+
if len(t.Subject) > 1000000 {
551
+
return xerrors.Errorf("Value in field t.Subject was too long")
552
+
}
553
+
554
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Subject))); err != nil {
555
+
return err
556
+
}
557
+
if _, err := cw.WriteString(string(t.Subject)); err != nil {
558
+
return err
559
+
}
560
+
561
+
// t.Reaction (string) (string)
562
+
if len("reaction") > 1000000 {
563
+
return xerrors.Errorf("Value in field \"reaction\" was too long")
564
+
}
565
+
566
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("reaction"))); err != nil {
567
+
return err
568
+
}
569
+
if _, err := cw.WriteString(string("reaction")); err != nil {
570
+
return err
571
+
}
572
+
573
+
if len(t.Reaction) > 1000000 {
574
+
return xerrors.Errorf("Value in field t.Reaction was too long")
575
+
}
576
+
577
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Reaction))); err != nil {
578
+
return err
579
+
}
580
+
if _, err := cw.WriteString(string(t.Reaction)); err != nil {
581
+
return err
582
+
}
583
+
584
+
// t.CreatedAt (string) (string)
585
+
if len("createdAt") > 1000000 {
586
+
return xerrors.Errorf("Value in field \"createdAt\" was too long")
587
+
}
588
+
589
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
590
+
return err
591
+
}
592
+
if _, err := cw.WriteString(string("createdAt")); err != nil {
593
+
return err
594
+
}
595
+
596
+
if len(t.CreatedAt) > 1000000 {
597
+
return xerrors.Errorf("Value in field t.CreatedAt was too long")
598
+
}
599
+
600
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
601
+
return err
602
+
}
603
+
if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
604
+
return err
605
+
}
606
+
return nil
607
+
}
608
+
609
+
func (t *FeedReaction) UnmarshalCBOR(r io.Reader) (err error) {
610
+
*t = FeedReaction{}
611
+
612
+
cr := cbg.NewCborReader(r)
613
+
614
+
maj, extra, err := cr.ReadHeader()
615
+
if err != nil {
616
+
return err
617
+
}
618
+
defer func() {
619
+
if err == io.EOF {
620
+
err = io.ErrUnexpectedEOF
621
+
}
622
+
}()
623
+
624
+
if maj != cbg.MajMap {
625
+
return fmt.Errorf("cbor input should be of type map")
626
+
}
627
+
628
+
if extra > cbg.MaxLength {
629
+
return fmt.Errorf("FeedReaction: map struct too large (%d)", extra)
630
+
}
631
+
632
+
n := extra
633
+
634
+
nameBuf := make([]byte, 9)
635
+
for i := uint64(0); i < n; i++ {
636
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
637
+
if err != nil {
638
+
return err
639
+
}
640
+
641
+
if !ok {
642
+
// Field doesn't exist on this type, so ignore it
643
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
644
+
return err
645
+
}
646
+
continue
647
+
}
648
+
649
+
switch string(nameBuf[:nameLen]) {
650
+
// t.LexiconTypeID (string) (string)
651
+
case "$type":
652
+
653
+
{
654
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
655
+
if err != nil {
656
+
return err
657
+
}
658
+
659
+
t.LexiconTypeID = string(sval)
660
+
}
661
+
// t.Subject (string) (string)
662
+
case "subject":
663
+
664
+
{
665
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
666
+
if err != nil {
667
+
return err
668
+
}
669
+
670
+
t.Subject = string(sval)
671
+
}
672
+
// t.Reaction (string) (string)
673
+
case "reaction":
674
+
675
+
{
676
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
677
+
if err != nil {
678
+
return err
679
+
}
680
+
681
+
t.Reaction = string(sval)
682
+
}
683
+
// t.CreatedAt (string) (string)
684
+
case "createdAt":
685
+
686
+
{
687
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
688
+
if err != nil {
689
+
return err
690
+
}
691
+
692
+
t.CreatedAt = string(sval)
693
+
}
694
+
695
+
default:
696
+
// Field doesn't exist on this type, so ignore it
697
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
698
+
return err
699
+
}
700
+
}
701
+
}
702
+
703
+
return nil
704
+
}
507
705
func (t *FeedStar) MarshalCBOR(w io.Writer) error {
508
706
if t == nil {
509
707
_, err := w.Write(cbg.CborNull)
···
2188
2386
2189
2387
return nil
2190
2388
}
2191
-
func (t *Pipeline_Dependencies_Elem) MarshalCBOR(w io.Writer) error {
2389
+
func (t *Pipeline_Dependency) MarshalCBOR(w io.Writer) error {
2192
2390
if t == nil {
2193
2391
_, err := w.Write(cbg.CborNull)
2194
2392
return err
···
2258
2456
return nil
2259
2457
}
2260
2458
2261
-
func (t *Pipeline_Dependencies_Elem) UnmarshalCBOR(r io.Reader) (err error) {
2262
-
*t = Pipeline_Dependencies_Elem{}
2459
+
func (t *Pipeline_Dependency) UnmarshalCBOR(r io.Reader) (err error) {
2460
+
*t = Pipeline_Dependency{}
2263
2461
2264
2462
cr := cbg.NewCborReader(r)
2265
2463
···
2278
2476
}
2279
2477
2280
2478
if extra > cbg.MaxLength {
2281
-
return fmt.Errorf("Pipeline_Dependencies_Elem: map struct too large (%d)", extra)
2479
+
return fmt.Errorf("Pipeline_Dependency: map struct too large (%d)", extra)
2282
2480
}
2283
2481
2284
2482
n := extra
···
2378
2576
return err
2379
2577
}
2380
2578
2381
-
// t.Inputs ([]*tangled.Pipeline_ManualTriggerData_Inputs_Elem) (slice)
2579
+
// t.Inputs ([]*tangled.Pipeline_Pair) (slice)
2382
2580
if t.Inputs != nil {
2383
2581
2384
2582
if len("inputs") > 1000000 {
···
2450
2648
}
2451
2649
2452
2650
switch string(nameBuf[:nameLen]) {
2453
-
// t.Inputs ([]*tangled.Pipeline_ManualTriggerData_Inputs_Elem) (slice)
2651
+
// t.Inputs ([]*tangled.Pipeline_Pair) (slice)
2454
2652
case "inputs":
2455
2653
2456
2654
maj, extra, err = cr.ReadHeader()
···
2467
2665
}
2468
2666
2469
2667
if extra > 0 {
2470
-
t.Inputs = make([]*Pipeline_ManualTriggerData_Inputs_Elem, extra)
2668
+
t.Inputs = make([]*Pipeline_Pair, extra)
2471
2669
}
2472
2670
2473
2671
for i := 0; i < int(extra); i++ {
···
2489
2687
if err := cr.UnreadByte(); err != nil {
2490
2688
return err
2491
2689
}
2492
-
t.Inputs[i] = new(Pipeline_ManualTriggerData_Inputs_Elem)
2690
+
t.Inputs[i] = new(Pipeline_Pair)
2493
2691
if err := t.Inputs[i].UnmarshalCBOR(cr); err != nil {
2494
2692
return xerrors.Errorf("unmarshaling t.Inputs[i] pointer: %w", err)
2495
2693
}
···
2510
2708
2511
2709
return nil
2512
2710
}
2513
-
func (t *Pipeline_ManualTriggerData_Inputs_Elem) MarshalCBOR(w io.Writer) error {
2711
+
func (t *Pipeline_Pair) MarshalCBOR(w io.Writer) error {
2514
2712
if t == nil {
2515
2713
_, err := w.Write(cbg.CborNull)
2516
2714
return err
···
2570
2768
return nil
2571
2769
}
2572
2770
2573
-
func (t *Pipeline_ManualTriggerData_Inputs_Elem) UnmarshalCBOR(r io.Reader) (err error) {
2574
-
*t = Pipeline_ManualTriggerData_Inputs_Elem{}
2771
+
func (t *Pipeline_Pair) UnmarshalCBOR(r io.Reader) (err error) {
2772
+
*t = Pipeline_Pair{}
2575
2773
2576
2774
cr := cbg.NewCborReader(r)
2577
2775
···
2590
2788
}
2591
2789
2592
2790
if extra > cbg.MaxLength {
2593
-
return fmt.Errorf("Pipeline_ManualTriggerData_Inputs_Elem: map struct too large (%d)", extra)
2791
+
return fmt.Errorf("Pipeline_Pair: map struct too large (%d)", extra)
2594
2792
}
2595
2793
2596
2794
n := extra
···
3014
3212
3015
3213
return nil
3016
3214
}
3017
-
3018
-
func (t *Pipeline_Step_Environment_Elem) MarshalCBOR(w io.Writer) error {
3019
-
if t == nil {
3020
-
_, err := w.Write(cbg.CborNull)
3021
-
return err
3022
-
}
3023
-
3024
-
cw := cbg.NewCborWriter(w)
3025
-
3026
-
if _, err := cw.Write([]byte{162}); err != nil {
3027
-
return err
3028
-
}
3029
-
3030
-
// t.Key (string) (string)
3031
-
if len("key") > 1000000 {
3032
-
return xerrors.Errorf("Value in field \"key\" was too long")
3033
-
}
3034
-
3035
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("key"))); err != nil {
3036
-
return err
3037
-
}
3038
-
if _, err := cw.WriteString(string("key")); err != nil {
3039
-
return err
3040
-
}
3041
-
3042
-
if len(t.Key) > 1000000 {
3043
-
return xerrors.Errorf("Value in field t.Key was too long")
3044
-
}
3045
-
3046
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Key))); err != nil {
3047
-
return err
3048
-
}
3049
-
if _, err := cw.WriteString(string(t.Key)); err != nil {
3050
-
return err
3051
-
}
3052
-
3053
-
// t.Value (string) (string)
3054
-
if len("value") > 1000000 {
3055
-
return xerrors.Errorf("Value in field \"value\" was too long")
3056
-
}
3057
-
3058
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("value"))); err != nil {
3059
-
return err
3060
-
}
3061
-
if _, err := cw.WriteString(string("value")); err != nil {
3062
-
return err
3063
-
}
3064
-
3065
-
if len(t.Value) > 1000000 {
3066
-
return xerrors.Errorf("Value in field t.Value was too long")
3067
-
}
3068
-
3069
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Value))); err != nil {
3070
-
return err
3071
-
}
3072
-
if _, err := cw.WriteString(string(t.Value)); err != nil {
3073
-
return err
3074
-
}
3075
-
return nil
3076
-
}
3077
-
3078
-
func (t *Pipeline_Step_Environment_Elem) UnmarshalCBOR(r io.Reader) (err error) {
3079
-
*t = Pipeline_Step_Environment_Elem{}
3080
-
3081
-
cr := cbg.NewCborReader(r)
3082
-
3083
-
maj, extra, err := cr.ReadHeader()
3084
-
if err != nil {
3085
-
return err
3086
-
}
3087
-
defer func() {
3088
-
if err == io.EOF {
3089
-
err = io.ErrUnexpectedEOF
3090
-
}
3091
-
}()
3092
-
3093
-
if maj != cbg.MajMap {
3094
-
return fmt.Errorf("cbor input should be of type map")
3095
-
}
3096
-
3097
-
if extra > cbg.MaxLength {
3098
-
return fmt.Errorf("Pipeline_Step_Environment_Elem: map struct too large (%d)", extra)
3099
-
}
3100
-
3101
-
n := extra
3102
-
3103
-
nameBuf := make([]byte, 5)
3104
-
for i := uint64(0); i < n; i++ {
3105
-
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
3106
-
if err != nil {
3107
-
return err
3108
-
}
3109
-
3110
-
if !ok {
3111
-
// Field doesn't exist on this type, so ignore it
3112
-
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
3113
-
return err
3114
-
}
3115
-
continue
3116
-
}
3117
-
3118
-
switch string(nameBuf[:nameLen]) {
3119
-
// t.Key (string) (string)
3120
-
case "key":
3121
-
3122
-
{
3123
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3124
-
if err != nil {
3125
-
return err
3126
-
}
3127
-
3128
-
t.Key = string(sval)
3129
-
}
3130
-
// t.Value (string) (string)
3131
-
case "value":
3132
-
3133
-
{
3134
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
3135
-
if err != nil {
3136
-
return err
3137
-
}
3138
-
3139
-
t.Value = string(sval)
3140
-
}
3141
-
3142
-
default:
3143
-
// Field doesn't exist on this type, so ignore it
3144
-
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
3145
-
return err
3146
-
}
3147
-
}
3148
-
}
3149
-
3150
-
return nil
3151
-
}
3152
3215
func (t *PipelineStatus) MarshalCBOR(w io.Writer) error {
3153
3216
if t == nil {
3154
3217
_, err := w.Write(cbg.CborNull)
···
3511
3574
3512
3575
return nil
3513
3576
}
3514
-
3515
3577
func (t *Pipeline_Step) MarshalCBOR(w io.Writer) error {
3516
3578
if t == nil {
3517
3579
_, err := w.Write(cbg.CborNull)
···
3575
3637
return err
3576
3638
}
3577
3639
3578
-
// t.Environment ([]*tangled.Pipeline_Step_Environment_Elem) (slice)
3640
+
// t.Environment ([]*tangled.Pipeline_Pair) (slice)
3579
3641
if t.Environment != nil {
3580
3642
3581
3643
if len("environment") > 1000000 {
···
3669
3731
3670
3732
t.Command = string(sval)
3671
3733
}
3672
-
// t.Environment ([]*tangled.Pipeline_Step_Environment_Elem) (slice)
3734
+
// t.Environment ([]*tangled.Pipeline_Pair) (slice)
3673
3735
case "environment":
3674
3736
3675
3737
maj, extra, err = cr.ReadHeader()
···
3686
3748
}
3687
3749
3688
3750
if extra > 0 {
3689
-
t.Environment = make([]*Pipeline_Step_Environment_Elem, extra)
3751
+
t.Environment = make([]*Pipeline_Pair, extra)
3690
3752
}
3691
3753
3692
3754
for i := 0; i < int(extra); i++ {
···
3708
3770
if err := cr.UnreadByte(); err != nil {
3709
3771
return err
3710
3772
}
3711
-
t.Environment[i] = new(Pipeline_Step_Environment_Elem)
3773
+
t.Environment[i] = new(Pipeline_Pair)
3712
3774
if err := t.Environment[i].UnmarshalCBOR(cr); err != nil {
3713
3775
return xerrors.Errorf("unmarshaling t.Environment[i] pointer: %w", err)
3714
3776
}
···
4274
4336
4275
4337
}
4276
4338
4277
-
// t.Environment ([]*tangled.Pipeline_Workflow_Environment_Elem) (slice)
4339
+
// t.Environment ([]*tangled.Pipeline_Pair) (slice)
4278
4340
if len("environment") > 1000000 {
4279
4341
return xerrors.Errorf("Value in field \"environment\" was too long")
4280
4342
}
···
4300
4362
4301
4363
}
4302
4364
4303
-
// t.Dependencies ([]tangled.Pipeline_Dependencies_Elem) (slice)
4365
+
// t.Dependencies ([]*tangled.Pipeline_Dependency) (slice)
4304
4366
if len("dependencies") > 1000000 {
4305
4367
return xerrors.Errorf("Value in field \"dependencies\" was too long")
4306
4368
}
···
4449
4511
4450
4512
}
4451
4513
}
4452
-
// t.Environment ([]*tangled.Pipeline_Workflow_Environment_Elem) (slice)
4514
+
// t.Environment ([]*tangled.Pipeline_Pair) (slice)
4453
4515
case "environment":
4454
4516
4455
4517
maj, extra, err = cr.ReadHeader()
···
4466
4528
}
4467
4529
4468
4530
if extra > 0 {
4469
-
t.Environment = make([]*Pipeline_Workflow_Environment_Elem, extra)
4531
+
t.Environment = make([]*Pipeline_Pair, extra)
4470
4532
}
4471
4533
4472
4534
for i := 0; i < int(extra); i++ {
···
4488
4550
if err := cr.UnreadByte(); err != nil {
4489
4551
return err
4490
4552
}
4491
-
t.Environment[i] = new(Pipeline_Workflow_Environment_Elem)
4553
+
t.Environment[i] = new(Pipeline_Pair)
4492
4554
if err := t.Environment[i].UnmarshalCBOR(cr); err != nil {
4493
4555
return xerrors.Errorf("unmarshaling t.Environment[i] pointer: %w", err)
4494
4556
}
···
4498
4560
4499
4561
}
4500
4562
}
4501
-
// t.Dependencies ([]tangled.Pipeline_Dependencies_Elem) (slice)
4563
+
// t.Dependencies ([]*tangled.Pipeline_Dependency) (slice)
4502
4564
case "dependencies":
4503
4565
4504
4566
maj, extra, err = cr.ReadHeader()
···
4515
4577
}
4516
4578
4517
4579
if extra > 0 {
4518
-
t.Dependencies = make([]Pipeline_Dependencies_Elem, extra)
4580
+
t.Dependencies = make([]*Pipeline_Dependency, extra)
4519
4581
}
4520
4582
4521
4583
for i := 0; i < int(extra); i++ {
···
4529
4591
4530
4592
{
4531
4593
4532
-
if err := t.Dependencies[i].UnmarshalCBOR(cr); err != nil {
4533
-
return xerrors.Errorf("unmarshaling t.Dependencies[i]: %w", err)
4594
+
b, err := cr.ReadByte()
4595
+
if err != nil {
4596
+
return err
4597
+
}
4598
+
if b != cbg.CborNull[0] {
4599
+
if err := cr.UnreadByte(); err != nil {
4600
+
return err
4601
+
}
4602
+
t.Dependencies[i] = new(Pipeline_Dependency)
4603
+
if err := t.Dependencies[i].UnmarshalCBOR(cr); err != nil {
4604
+
return xerrors.Errorf("unmarshaling t.Dependencies[i] pointer: %w", err)
4605
+
}
4534
4606
}
4535
4607
4536
4608
}
4537
4609
4538
4610
}
4539
-
}
4540
-
4541
-
default:
4542
-
// Field doesn't exist on this type, so ignore it
4543
-
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
4544
-
return err
4545
-
}
4546
-
}
4547
-
}
4548
-
4549
-
return nil
4550
-
}
4551
-
func (t *Pipeline_Workflow_Environment_Elem) MarshalCBOR(w io.Writer) error {
4552
-
if t == nil {
4553
-
_, err := w.Write(cbg.CborNull)
4554
-
return err
4555
-
}
4556
-
4557
-
cw := cbg.NewCborWriter(w)
4558
-
4559
-
if _, err := cw.Write([]byte{162}); err != nil {
4560
-
return err
4561
-
}
4562
-
4563
-
// t.Key (string) (string)
4564
-
if len("key") > 1000000 {
4565
-
return xerrors.Errorf("Value in field \"key\" was too long")
4566
-
}
4567
-
4568
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("key"))); err != nil {
4569
-
return err
4570
-
}
4571
-
if _, err := cw.WriteString(string("key")); err != nil {
4572
-
return err
4573
-
}
4574
-
4575
-
if len(t.Key) > 1000000 {
4576
-
return xerrors.Errorf("Value in field t.Key was too long")
4577
-
}
4578
-
4579
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Key))); err != nil {
4580
-
return err
4581
-
}
4582
-
if _, err := cw.WriteString(string(t.Key)); err != nil {
4583
-
return err
4584
-
}
4585
-
4586
-
// t.Value (string) (string)
4587
-
if len("value") > 1000000 {
4588
-
return xerrors.Errorf("Value in field \"value\" was too long")
4589
-
}
4590
-
4591
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("value"))); err != nil {
4592
-
return err
4593
-
}
4594
-
if _, err := cw.WriteString(string("value")); err != nil {
4595
-
return err
4596
-
}
4597
-
4598
-
if len(t.Value) > 1000000 {
4599
-
return xerrors.Errorf("Value in field t.Value was too long")
4600
-
}
4601
-
4602
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Value))); err != nil {
4603
-
return err
4604
-
}
4605
-
if _, err := cw.WriteString(string(t.Value)); err != nil {
4606
-
return err
4607
-
}
4608
-
return nil
4609
-
}
4610
-
4611
-
func (t *Pipeline_Workflow_Environment_Elem) UnmarshalCBOR(r io.Reader) (err error) {
4612
-
*t = Pipeline_Workflow_Environment_Elem{}
4613
-
4614
-
cr := cbg.NewCborReader(r)
4615
-
4616
-
maj, extra, err := cr.ReadHeader()
4617
-
if err != nil {
4618
-
return err
4619
-
}
4620
-
defer func() {
4621
-
if err == io.EOF {
4622
-
err = io.ErrUnexpectedEOF
4623
-
}
4624
-
}()
4625
-
4626
-
if maj != cbg.MajMap {
4627
-
return fmt.Errorf("cbor input should be of type map")
4628
-
}
4629
-
4630
-
if extra > cbg.MaxLength {
4631
-
return fmt.Errorf("Pipeline_Workflow_Environment_Elem: map struct too large (%d)", extra)
4632
-
}
4633
-
4634
-
n := extra
4635
-
4636
-
nameBuf := make([]byte, 5)
4637
-
for i := uint64(0); i < n; i++ {
4638
-
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
4639
-
if err != nil {
4640
-
return err
4641
-
}
4642
-
4643
-
if !ok {
4644
-
// Field doesn't exist on this type, so ignore it
4645
-
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
4646
-
return err
4647
-
}
4648
-
continue
4649
-
}
4650
-
4651
-
switch string(nameBuf[:nameLen]) {
4652
-
// t.Key (string) (string)
4653
-
case "key":
4654
-
4655
-
{
4656
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
4657
-
if err != nil {
4658
-
return err
4659
-
}
4660
-
4661
-
t.Key = string(sval)
4662
-
}
4663
-
// t.Value (string) (string)
4664
-
case "value":
4665
-
4666
-
{
4667
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
4668
-
if err != nil {
4669
-
return err
4670
-
}
4671
-
4672
-
t.Value = string(sval)
4673
4611
}
4674
4612
4675
4613
default:
···
7268
7206
}
7269
7207
7270
7208
cw := cbg.NewCborWriter(w)
7271
-
fieldCount := 2
7209
+
fieldCount := 3
7272
7210
7273
7211
if t.Repo == nil {
7274
7212
fieldCount--
7275
7213
}
7276
7214
7277
7215
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
7216
+
return err
7217
+
}
7218
+
7219
+
// t.Sha (string) (string)
7220
+
if len("sha") > 1000000 {
7221
+
return xerrors.Errorf("Value in field \"sha\" was too long")
7222
+
}
7223
+
7224
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sha"))); err != nil {
7225
+
return err
7226
+
}
7227
+
if _, err := cw.WriteString(string("sha")); err != nil {
7228
+
return err
7229
+
}
7230
+
7231
+
if len(t.Sha) > 1000000 {
7232
+
return xerrors.Errorf("Value in field t.Sha was too long")
7233
+
}
7234
+
7235
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Sha))); err != nil {
7236
+
return err
7237
+
}
7238
+
if _, err := cw.WriteString(string(t.Sha)); err != nil {
7278
7239
return err
7279
7240
}
7280
7241
···
7376
7337
}
7377
7338
7378
7339
switch string(nameBuf[:nameLen]) {
7379
-
// t.Repo (string) (string)
7340
+
// t.Sha (string) (string)
7341
+
case "sha":
7342
+
7343
+
{
7344
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
7345
+
if err != nil {
7346
+
return err
7347
+
}
7348
+
7349
+
t.Sha = string(sval)
7350
+
}
7351
+
// t.Repo (string) (string)
7380
7352
case "repo":
7381
7353
7382
7354
{
+24
api/tangled/feedreaction.go
+24
api/tangled/feedreaction.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package tangled
4
+
5
+
// schema: sh.tangled.feed.reaction
6
+
7
+
import (
8
+
"github.com/bluesky-social/indigo/lex/util"
9
+
)
10
+
11
+
const (
12
+
FeedReactionNSID = "sh.tangled.feed.reaction"
13
+
)
14
+
15
+
func init() {
16
+
util.RegisterType("sh.tangled.feed.reaction", &FeedReaction{})
17
+
} //
18
+
// RECORDTYPE: FeedReaction
19
+
type FeedReaction struct {
20
+
LexiconTypeID string `json:"$type,const=sh.tangled.feed.reaction" cborgen:"$type,const=sh.tangled.feed.reaction"`
21
+
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
22
+
Reaction string `json:"reaction" cborgen:"reaction"`
23
+
Subject string `json:"subject" cborgen:"subject"`
24
+
}
+1
api/tangled/repopull.go
+1
api/tangled/repopull.go
+13
-21
api/tangled/tangledpipeline.go
+13
-21
api/tangled/tangledpipeline.go
···
29
29
Submodules bool `json:"submodules" cborgen:"submodules"`
30
30
}
31
31
32
-
type Pipeline_Dependencies_Elem struct {
32
+
// Pipeline_Dependency is a "dependency" in the sh.tangled.pipeline schema.
33
+
type Pipeline_Dependency struct {
33
34
Packages []string `json:"packages" cborgen:"packages"`
34
35
Registry string `json:"registry" cborgen:"registry"`
35
36
}
36
37
37
38
// Pipeline_ManualTriggerData is a "manualTriggerData" in the sh.tangled.pipeline schema.
38
39
type Pipeline_ManualTriggerData struct {
39
-
Inputs []*Pipeline_ManualTriggerData_Inputs_Elem `json:"inputs,omitempty" cborgen:"inputs,omitempty"`
40
+
Inputs []*Pipeline_Pair `json:"inputs,omitempty" cborgen:"inputs,omitempty"`
40
41
}
41
42
42
-
type Pipeline_ManualTriggerData_Inputs_Elem struct {
43
+
// Pipeline_Pair is a "pair" in the sh.tangled.pipeline schema.
44
+
type Pipeline_Pair struct {
43
45
Key string `json:"key" cborgen:"key"`
44
46
Value string `json:"value" cborgen:"value"`
45
47
}
···
61
63
62
64
// Pipeline_Step is a "step" in the sh.tangled.pipeline schema.
63
65
type Pipeline_Step struct {
64
-
Command string `json:"command" cborgen:"command"`
65
-
Environment []*Pipeline_Step_Environment_Elem `json:"environment,omitempty" cborgen:"environment,omitempty"`
66
-
Name string `json:"name" cborgen:"name"`
67
-
}
68
-
69
-
type Pipeline_Step_Environment_Elem struct {
70
-
Key string `json:"key" cborgen:"key"`
71
-
Value string `json:"value" cborgen:"value"`
66
+
Command string `json:"command" cborgen:"command"`
67
+
Environment []*Pipeline_Pair `json:"environment,omitempty" cborgen:"environment,omitempty"`
68
+
Name string `json:"name" cborgen:"name"`
72
69
}
73
70
74
71
// Pipeline_TriggerMetadata is a "triggerMetadata" in the sh.tangled.pipeline schema.
···
90
87
91
88
// Pipeline_Workflow is a "workflow" in the sh.tangled.pipeline schema.
92
89
type Pipeline_Workflow struct {
93
-
Clone *Pipeline_CloneOpts `json:"clone" cborgen:"clone"`
94
-
Dependencies []Pipeline_Dependencies_Elem `json:"dependencies" cborgen:"dependencies"`
95
-
Environment []*Pipeline_Workflow_Environment_Elem `json:"environment" cborgen:"environment"`
96
-
Name string `json:"name" cborgen:"name"`
97
-
Steps []*Pipeline_Step `json:"steps" cborgen:"steps"`
98
-
}
99
-
100
-
type Pipeline_Workflow_Environment_Elem struct {
101
-
Key string `json:"key" cborgen:"key"`
102
-
Value string `json:"value" cborgen:"value"`
90
+
Clone *Pipeline_CloneOpts `json:"clone" cborgen:"clone"`
91
+
Dependencies []*Pipeline_Dependency `json:"dependencies" cborgen:"dependencies"`
92
+
Environment []*Pipeline_Pair `json:"environment" cborgen:"environment"`
93
+
Name string `json:"name" cborgen:"name"`
94
+
Steps []*Pipeline_Step `json:"steps" cborgen:"steps"`
103
95
}
+1
-1
appview/db/artifact.go
+1
-1
appview/db/artifact.go
···
27
27
}
28
28
29
29
func (a *Artifact) ArtifactAt() syntax.ATURI {
30
-
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", a.Did, tangled.RepoPullNSID, a.Rkey))
30
+
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", a.Did, tangled.RepoArtifactNSID, a.Rkey))
31
31
}
32
32
33
33
func AddArtifact(e Execer, artifact Artifact) error {
+10
appview/db/db.go
+10
appview/db/db.go
···
199
199
unique(starred_by_did, repo_at)
200
200
);
201
201
202
+
create table if not exists reactions (
203
+
id integer primary key autoincrement,
204
+
reacted_by_did text not null,
205
+
thread_at text not null,
206
+
kind text not null,
207
+
rkey text not null,
208
+
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
209
+
unique(reacted_by_did, thread_at, kind)
210
+
);
211
+
202
212
create table if not exists emails (
203
213
id integer primary key autoincrement,
204
214
did text not null,
+2
-2
appview/db/issues.go
+2
-2
appview/db/issues.go
···
277
277
}
278
278
279
279
func GetIssueWithComments(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, []Comment, error) {
280
-
query := `select owner_did, issue_id, created, title, body, open from issues where repo_at = ? and issue_id = ?`
280
+
query := `select owner_did, issue_id, created, title, body, open, issue_at from issues where repo_at = ? and issue_id = ?`
281
281
row := e.QueryRow(query, repoAt, issueId)
282
282
283
283
var issue Issue
284
284
var createdAt string
285
-
err := row.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open)
285
+
err := row.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &issue.IssueAt)
286
286
if err != nil {
287
287
return nil, nil, err
288
288
}
+4
-3
appview/db/pipeline.go
+4
-3
appview/db/pipeline.go
···
9
9
"github.com/bluesky-social/indigo/atproto/syntax"
10
10
"github.com/go-git/go-git/v5/plumbing"
11
11
spindle "tangled.sh/tangled.sh/core/spindle/models"
12
+
"tangled.sh/tangled.sh/core/workflow"
12
13
)
13
14
14
15
type Pipeline struct {
···
85
86
86
87
type Trigger struct {
87
88
Id int
88
-
Kind string
89
+
Kind workflow.TriggerKind
89
90
90
91
// push trigger fields
91
92
PushRef *string
···
100
101
}
101
102
102
103
func (t *Trigger) IsPush() bool {
103
-
return t != nil && t.Kind == "push"
104
+
return t != nil && t.Kind == workflow.TriggerKindPush
104
105
}
105
106
106
107
func (t *Trigger) IsPullRequest() bool {
107
-
return t != nil && t.Kind == "pull_request"
108
+
return t != nil && t.Kind == workflow.TriggerKindPullRequest
108
109
}
109
110
110
111
func (t *Trigger) TargetRef() string {
+6
appview/db/pulls.go
+6
appview/db/pulls.go
···
87
87
if p.PullSource != nil {
88
88
s := p.PullSource.AsRecord()
89
89
source = &s
90
+
source.Sha = p.LatestSha()
90
91
}
91
92
92
93
record := tangled.RepoPull{
···
162
163
func (p *Pull) LatestPatch() string {
163
164
latestSubmission := p.Submissions[p.LastRoundNumber()]
164
165
return latestSubmission.Patch
166
+
}
167
+
168
+
func (p *Pull) LatestSha() string {
169
+
latestSubmission := p.Submissions[p.LastRoundNumber()]
170
+
return latestSubmission.SourceRev
165
171
}
166
172
167
173
func (p *Pull) PullAt() syntax.ATURI {
+141
appview/db/reaction.go
+141
appview/db/reaction.go
···
1
+
package db
2
+
3
+
import (
4
+
"log"
5
+
"time"
6
+
7
+
"github.com/bluesky-social/indigo/atproto/syntax"
8
+
)
9
+
10
+
type ReactionKind string
11
+
12
+
const (
13
+
Like ReactionKind = "๐"
14
+
Unlike = "๐"
15
+
Laugh = "๐"
16
+
Celebration = "๐"
17
+
Confused = "๐ซค"
18
+
Heart = "โค๏ธ"
19
+
Rocket = "๐"
20
+
Eyes = "๐"
21
+
)
22
+
23
+
func (rk ReactionKind) String() string {
24
+
return string(rk)
25
+
}
26
+
27
+
var OrderedReactionKinds = []ReactionKind{
28
+
Like,
29
+
Unlike,
30
+
Laugh,
31
+
Celebration,
32
+
Confused,
33
+
Heart,
34
+
Rocket,
35
+
Eyes,
36
+
}
37
+
38
+
func ParseReactionKind(raw string) (ReactionKind, bool) {
39
+
k, ok := (map[string]ReactionKind{
40
+
"๐": Like,
41
+
"๐": Unlike,
42
+
"๐": Laugh,
43
+
"๐": Celebration,
44
+
"๐ซค": Confused,
45
+
"โค๏ธ": Heart,
46
+
"๐": Rocket,
47
+
"๐": Eyes,
48
+
})[raw]
49
+
return k, ok
50
+
}
51
+
52
+
type Reaction struct {
53
+
ReactedByDid string
54
+
ThreadAt syntax.ATURI
55
+
Created time.Time
56
+
Rkey string
57
+
Kind ReactionKind
58
+
}
59
+
60
+
func AddReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind ReactionKind, rkey string) error {
61
+
query := `insert or ignore into reactions (reacted_by_did, thread_at, kind, rkey) values (?, ?, ?, ?)`
62
+
_, err := e.Exec(query, reactedByDid, threadAt, kind, rkey)
63
+
return err
64
+
}
65
+
66
+
// Get a reaction record
67
+
func GetReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind ReactionKind) (*Reaction, error) {
68
+
query := `
69
+
select reacted_by_did, thread_at, created, rkey
70
+
from reactions
71
+
where reacted_by_did = ? and thread_at = ? and kind = ?`
72
+
row := e.QueryRow(query, reactedByDid, threadAt, kind)
73
+
74
+
var reaction Reaction
75
+
var created string
76
+
err := row.Scan(&reaction.ReactedByDid, &reaction.ThreadAt, &created, &reaction.Rkey)
77
+
if err != nil {
78
+
return nil, err
79
+
}
80
+
81
+
createdAtTime, err := time.Parse(time.RFC3339, created)
82
+
if err != nil {
83
+
log.Println("unable to determine followed at time")
84
+
reaction.Created = time.Now()
85
+
} else {
86
+
reaction.Created = createdAtTime
87
+
}
88
+
89
+
return &reaction, nil
90
+
}
91
+
92
+
// Remove a reaction
93
+
func DeleteReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind ReactionKind) error {
94
+
_, err := e.Exec(`delete from reactions where reacted_by_did = ? and thread_at = ? and kind = ?`, reactedByDid, threadAt, kind)
95
+
return err
96
+
}
97
+
98
+
// Remove a reaction
99
+
func DeleteReactionByRkey(e Execer, reactedByDid string, rkey string) error {
100
+
_, err := e.Exec(`delete from reactions where reacted_by_did = ? and rkey = ?`, reactedByDid, rkey)
101
+
return err
102
+
}
103
+
104
+
func GetReactionCount(e Execer, threadAt syntax.ATURI, kind ReactionKind) (int, error) {
105
+
count := 0
106
+
err := e.QueryRow(
107
+
`select count(reacted_by_did) from reactions where thread_at = ? and kind = ?`, threadAt, kind).Scan(&count)
108
+
if err != nil {
109
+
return 0, err
110
+
}
111
+
return count, nil
112
+
}
113
+
114
+
func GetReactionCountMap(e Execer, threadAt syntax.ATURI) (map[ReactionKind]int, error) {
115
+
countMap := map[ReactionKind]int{}
116
+
for _, kind := range OrderedReactionKinds {
117
+
count, err := GetReactionCount(e, threadAt, kind)
118
+
if err != nil {
119
+
return map[ReactionKind]int{}, nil
120
+
}
121
+
countMap[kind] = count
122
+
}
123
+
return countMap, nil
124
+
}
125
+
126
+
func GetReactionStatus(e Execer, userDid string, threadAt syntax.ATURI, kind ReactionKind) bool {
127
+
if _, err := GetReaction(e, userDid, threadAt, kind); err != nil {
128
+
return false
129
+
} else {
130
+
return true
131
+
}
132
+
}
133
+
134
+
func GetReactionStatusMap(e Execer, userDid string, threadAt syntax.ATURI) map[ReactionKind]bool {
135
+
statusMap := map[ReactionKind]bool{}
136
+
for _, kind := range OrderedReactionKinds {
137
+
count := GetReactionStatus(e, userDid, threadAt, kind)
138
+
statusMap[kind] = count
139
+
}
140
+
return statusMap
141
+
}
+9
appview/idresolver/resolver.go
+9
appview/idresolver/resolver.go
+26
-20
appview/ingester.go
+26
-20
appview/ingester.go
···
40
40
}
41
41
}()
42
42
43
-
if e.Kind != models.EventKindCommit {
44
-
return nil
45
-
}
46
-
47
-
switch e.Commit.Collection {
48
-
case tangled.GraphFollowNSID:
49
-
err = i.ingestFollow(e)
50
-
case tangled.FeedStarNSID:
51
-
err = i.ingestStar(e)
52
-
case tangled.PublicKeyNSID:
53
-
err = i.ingestPublicKey(e)
54
-
case tangled.RepoArtifactNSID:
55
-
err = i.ingestArtifact(e)
56
-
case tangled.ActorProfileNSID:
57
-
err = i.ingestProfile(e)
58
-
case tangled.SpindleMemberNSID:
59
-
err = i.ingestSpindleMember(e)
60
-
case tangled.SpindleNSID:
61
-
err = i.ingestSpindle(e)
43
+
l := i.Logger.With("kind", e.Kind)
44
+
switch e.Kind {
45
+
case models.EventKindAccount:
46
+
if !e.Account.Active && *e.Account.Status == "deactivated" {
47
+
err = i.IdResolver.InvalidateIdent(ctx, e.Account.Did)
48
+
}
49
+
case models.EventKindIdentity:
50
+
err = i.IdResolver.InvalidateIdent(ctx, e.Identity.Did)
51
+
case models.EventKindCommit:
52
+
switch e.Commit.Collection {
53
+
case tangled.GraphFollowNSID:
54
+
err = i.ingestFollow(e)
55
+
case tangled.FeedStarNSID:
56
+
err = i.ingestStar(e)
57
+
case tangled.PublicKeyNSID:
58
+
err = i.ingestPublicKey(e)
59
+
case tangled.RepoArtifactNSID:
60
+
err = i.ingestArtifact(e)
61
+
case tangled.ActorProfileNSID:
62
+
err = i.ingestProfile(e)
63
+
case tangled.SpindleMemberNSID:
64
+
err = i.ingestSpindleMember(e)
65
+
case tangled.SpindleNSID:
66
+
err = i.ingestSpindle(e)
67
+
}
68
+
l = i.Logger.With("nsid", e.Commit.Collection)
62
69
}
63
70
64
71
if err != nil {
65
-
l := i.Logger.With("nsid", e.Commit.Collection)
66
72
l.Error("error ingesting record", "err", err)
67
73
}
68
74
+16
appview/issues/issues.go
+16
appview/issues/issues.go
···
11
11
12
12
comatproto "github.com/bluesky-social/indigo/api/atproto"
13
13
"github.com/bluesky-social/indigo/atproto/data"
14
+
"github.com/bluesky-social/indigo/atproto/syntax"
14
15
lexutil "github.com/bluesky-social/indigo/lex/util"
15
16
"github.com/go-chi/chi/v5"
16
17
"github.com/posthog/posthog-go"
···
79
80
return
80
81
}
81
82
83
+
reactionCountMap, err := db.GetReactionCountMap(rp.db, syntax.ATURI(issue.IssueAt))
84
+
if err != nil {
85
+
log.Println("failed to get issue reactions")
86
+
rp.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
87
+
}
88
+
89
+
userReactions := map[db.ReactionKind]bool{}
90
+
if user != nil {
91
+
userReactions = db.GetReactionStatusMap(rp.db, user.Did, syntax.ATURI(issue.IssueAt))
92
+
}
93
+
82
94
issueOwnerIdent, err := rp.idResolver.ResolveIdent(r.Context(), issue.OwnerDid)
83
95
if err != nil {
84
96
log.Println("failed to resolve issue owner", err)
···
106
118
107
119
IssueOwnerHandle: issueOwnerIdent.Handle.String(),
108
120
DidHandleMap: didHandleMap,
121
+
122
+
OrderedReactionKinds: db.OrderedReactionKinds,
123
+
Reactions: reactionCountMap,
124
+
UserReacted: userReactions,
109
125
})
110
126
111
127
}
+36
-14
appview/pages/pages.go
+36
-14
appview/pages/pages.go
···
690
690
IssueOwnerHandle string
691
691
DidHandleMap map[string]string
692
692
693
+
OrderedReactionKinds []db.ReactionKind
694
+
Reactions map[db.ReactionKind]int
695
+
UserReacted map[db.ReactionKind]bool
696
+
693
697
State string
698
+
}
699
+
700
+
type ThreadReactionFragmentParams struct {
701
+
ThreadAt syntax.ATURI
702
+
Kind db.ReactionKind
703
+
Count int
704
+
IsReacted bool
705
+
}
706
+
707
+
func (p *Pages) ThreadReactionFragment(w io.Writer, params ThreadReactionFragmentParams) error {
708
+
return p.executePlain("repo/fragments/reaction", w, params)
694
709
}
695
710
696
711
func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error {
···
797
812
AbandonedPulls []*db.Pull
798
813
MergeCheck types.MergeCheckResponse
799
814
ResubmitCheck ResubmitResult
815
+
Pipelines map[string]db.Pipeline
816
+
817
+
OrderedReactionKinds []db.ReactionKind
818
+
Reactions map[db.ReactionKind]int
819
+
UserReacted map[db.ReactionKind]bool
800
820
}
801
821
802
822
func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error {
···
805
825
}
806
826
807
827
type RepoPullPatchParams struct {
808
-
LoggedInUser *oauth.User
809
-
DidHandleMap map[string]string
810
-
RepoInfo repoinfo.RepoInfo
811
-
Pull *db.Pull
812
-
Stack db.Stack
813
-
Diff *types.NiceDiff
814
-
Round int
815
-
Submission *db.PullSubmission
828
+
LoggedInUser *oauth.User
829
+
DidHandleMap map[string]string
830
+
RepoInfo repoinfo.RepoInfo
831
+
Pull *db.Pull
832
+
Stack db.Stack
833
+
Diff *types.NiceDiff
834
+
Round int
835
+
Submission *db.PullSubmission
836
+
OrderedReactionKinds []db.ReactionKind
816
837
}
817
838
818
839
// this name is a mouthful
···
821
842
}
822
843
823
844
type RepoPullInterdiffParams struct {
824
-
LoggedInUser *oauth.User
825
-
DidHandleMap map[string]string
826
-
RepoInfo repoinfo.RepoInfo
827
-
Pull *db.Pull
828
-
Round int
829
-
Interdiff *patchutil.InterdiffResult
845
+
LoggedInUser *oauth.User
846
+
DidHandleMap map[string]string
847
+
RepoInfo repoinfo.RepoInfo
848
+
Pull *db.Pull
849
+
Round int
850
+
Interdiff *patchutil.InterdiffResult
851
+
OrderedReactionKinds []db.ReactionKind
830
852
}
831
853
832
854
// this name is a mouthful
+1
-1
appview/pages/templates/layouts/repobase.html
+1
-1
appview/pages/templates/layouts/repobase.html
+11
-10
appview/pages/templates/layouts/topbar.html
+11
-10
appview/pages/templates/layouts/topbar.html
···
1
1
{{ define "layouts/topbar" }}
2
2
<nav class="space-x-4 mb-4 px-6 py-2 rounded bg-white dark:bg-gray-800 dark:text-white drop-shadow-sm">
3
-
<div class="container flex justify-between p-0">
3
+
<div class="container flex justify-between p-0 items-center">
4
4
<div id="left-items">
5
5
<a href="/" hx-boost="true" class="flex gap-2 font-semibold italic">
6
6
tangled<sub>alpha</sub>
···
19
19
{{ i "code" "size-4" }} source
20
20
</a>
21
21
</div>
22
-
<div id="right-items" class="flex gap-2">
22
+
<div id="right-items" class="flex items-center gap-4">
23
23
{{ with .LoggedInUser }}
24
-
<a href="/repo/new" hx-boost="true">
25
-
{{ i "plus" "w-6 h-6" }}
24
+
<a href="/repo/new" hx-boost="true" class="btn-create hover:no-underline hover:text-white">
25
+
{{ i "plus" "w-4 h-4" }}
26
26
</a>
27
27
{{ block "dropDown" . }} {{ end }}
28
28
{{ else }}
···
36
36
{{ define "dropDown" }}
37
37
<details class="relative inline-block text-left">
38
38
<summary
39
-
class="cursor-pointer list-none"
39
+
class="cursor-pointer list-none flex items-center"
40
40
>
41
-
{{ didOrHandle .Did .Handle }}
41
+
{{ $user := didOrHandle .Did .Handle }}
42
+
{{ template "user/fragments/picHandleLink" $user }}
42
43
</summary>
43
44
<div
44
45
class="absolute flex flex-col right-0 mt-4 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700"
45
46
>
46
-
<a href="/{{ didOrHandle .Did .Handle }}">profile</a>
47
+
<a href="/{{ $user }}">profile</a>
47
48
<a href="/knots">knots</a>
48
49
<a href="/spindles">spindles</a>
49
50
<a href="/settings">settings</a>
50
-
<a href="#"
51
-
hx-post="/logout"
52
-
hx-swap="none"
51
+
<a href="#"
52
+
hx-post="/logout"
53
+
hx-swap="none"
53
54
class="text-red-400 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300">
54
55
logout
55
56
</a>
+1
-1
appview/pages/templates/repo/commit.html
+1
-1
appview/pages/templates/repo/commit.html
···
59
59
<div class="flex items-center gap-2 my-2">
60
60
{{ i "user" "w-4 h-4" }}
61
61
{{ $committerDidOrHandle := index $.EmailToDidOrHandle $commit.Committer.Email }}
62
-
<a href="/{{ $committerDidOrHandle }}">{{ $committerDidOrHandle }}</a>
62
+
<a href="/{{ $committerDidOrHandle }}">{{ template "user/fragments/picHandleLink" $committerDidOrHandle }}</a>
63
63
</div>
64
64
<div class="my-1 pt-2 text-xs border-t">
65
65
<div class="text-gray-600 dark:text-gray-300">SSH Key Fingerprint:</div>
+34
appview/pages/templates/repo/fragments/reaction.html
+34
appview/pages/templates/repo/fragments/reaction.html
···
1
+
{{ define "repo/fragments/reaction" }}
2
+
<button
3
+
id="reactIndi-{{ .Kind }}"
4
+
class="flex justify-center items-center min-w-8 min-h-8 rounded border
5
+
leading-4 px-3 gap-1
6
+
{{ if eq .Count 0 }}
7
+
hidden
8
+
{{ end }}
9
+
{{ if .IsReacted }}
10
+
bg-sky-100
11
+
border-sky-400
12
+
dark:bg-sky-900
13
+
dark:border-sky-500
14
+
{{ else }}
15
+
border-gray-200
16
+
hover:bg-gray-50
17
+
hover:border-gray-300
18
+
dark:border-gray-700
19
+
dark:hover:bg-gray-700
20
+
dark:hover:border-gray-600
21
+
{{ end }}
22
+
"
23
+
{{ if .IsReacted }}
24
+
hx-delete="/react?subject={{ .ThreadAt }}&kind={{ .Kind }}"
25
+
{{ else }}
26
+
hx-post="/react?subject={{ .ThreadAt }}&kind={{ .Kind }}"
27
+
{{ end }}
28
+
hx-swap="outerHTML"
29
+
hx-trigger="click from:(#reactBtn-{{ .Kind }}, #reactIndi-{{ .Kind }})"
30
+
hx-disabled-elt="this"
31
+
>
32
+
<span>{{ .Kind }}</span> <span>{{ .Count }}</span>
33
+
</button>
34
+
{{ end }}
+30
appview/pages/templates/repo/fragments/reactionsPopUp.html
+30
appview/pages/templates/repo/fragments/reactionsPopUp.html
···
1
+
{{ define "repo/fragments/reactionsPopUp" }}
2
+
<details
3
+
id="reactionsPopUp"
4
+
class="relative inline-block"
5
+
>
6
+
<summary
7
+
class="flex justify-center items-center min-w-8 min-h-8 rounded border border-gray-200 dark:border-gray-700
8
+
hover:bg-gray-50
9
+
hover:border-gray-300
10
+
dark:hover:bg-gray-700
11
+
dark:hover:border-gray-600
12
+
cursor-pointer list-none"
13
+
>
14
+
{{ i "smile" "size-4" }}
15
+
</summary>
16
+
<div
17
+
class="absolute flex left-0 z-10 mt-4 rounded bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 shadow-lg"
18
+
>
19
+
{{ range $kind := . }}
20
+
<button
21
+
id="reactBtn-{{ $kind }}"
22
+
class="size-12 hover:bg-gray-100 dark:hover:bg-gray-700"
23
+
hx-on:click="this.parentElement.parentElement.removeAttribute('open')"
24
+
>
25
+
{{ $kind }}
26
+
</button>
27
+
{{ end }}
28
+
</div>
29
+
</details>
30
+
{{ end }}
+1
-1
appview/pages/templates/repo/index.html
+1
-1
appview/pages/templates/repo/index.html
+6
-6
appview/pages/templates/repo/issues/fragments/issueComment.html
+6
-6
appview/pages/templates/repo/issues/fragments/issueComment.html
···
1
1
{{ define "repo/issues/fragments/issueComment" }}
2
2
{{ with .Comment }}
3
3
<div id="comment-container-{{.CommentId}}">
4
-
<div class="flex items-center gap-2 mb-2 text-gray-500 dark:text-gray-400 text-sm">
4
+
<div class="flex items-center gap-2 mb-2 text-gray-500 dark:text-gray-400 text-sm flex-wrap">
5
5
{{ $owner := index $.DidHandleMap .OwnerDid }}
6
-
<a href="/{{ $owner }}" class="no-underline hover:underline">{{ $owner }}</a>
6
+
{{ template "user/fragments/picHandleLink" $owner }}
7
7
8
8
<span class="before:content-['ยท']"></span>
9
9
<a
···
18
18
{{ .Created | timeFmt }}
19
19
{{ end }}
20
20
</a>
21
-
21
+
22
22
<!-- show user "hats" -->
23
23
{{ $isIssueAuthor := eq .OwnerDid $.Issue.OwnerDid }}
24
24
{{ if $isIssueAuthor }}
···
29
29
30
30
{{ $isCommentOwner := and $.LoggedInUser (eq $.LoggedInUser.Did .OwnerDid) }}
31
31
{{ if and $isCommentOwner (not .Deleted) }}
32
-
<button
33
-
class="btn px-2 py-1 text-sm"
32
+
<button
33
+
class="btn px-2 py-1 text-sm"
34
34
hx-get="/{{ $.RepoInfo.FullName }}/issues/{{ .Issue }}/comment/{{ .CommentId }}/edit"
35
35
hx-swap="outerHTML"
36
36
hx-target="#comment-container-{{.CommentId}}"
37
37
>
38
38
{{ i "pencil" "w-4 h-4" }}
39
39
</button>
40
-
<button
40
+
<button
41
41
class="btn px-2 py-1 text-sm text-red-500 flex gap-2 items-center group"
42
42
hx-delete="/{{ $.RepoInfo.FullName }}/issues/{{ .Issue }}/comment/{{ .CommentId }}/"
43
43
hx-confirm="Are you sure you want to delete your comment?"
+16
-2
appview/pages/templates/repo/issues/issue.html
+16
-2
appview/pages/templates/repo/issues/issue.html
···
33
33
<span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1">
34
34
opened by
35
35
{{ $owner := didOrHandle .Issue.OwnerDid .IssueOwnerHandle }}
36
-
{{ template "user/fragments/picHandle" $owner }}
36
+
{{ template "user/fragments/picHandleLink" $owner }}
37
37
<span class="select-none before:content-['\00B7']"></span>
38
38
<time title="{{ .Issue.Created | longTimeFmt }}">
39
39
{{ .Issue.Created | timeFmt }}
···
46
46
{{ .Issue.Body | markdown }}
47
47
</article>
48
48
{{ end }}
49
+
50
+
<div class="flex items-center gap-2 mt-2">
51
+
{{ template "repo/fragments/reactionsPopUp" .OrderedReactionKinds }}
52
+
{{ range $kind := .OrderedReactionKinds }}
53
+
{{
54
+
template "repo/fragments/reaction"
55
+
(dict
56
+
"Kind" $kind
57
+
"Count" (index $.Reactions $kind)
58
+
"IsReacted" (index $.UserReacted $kind)
59
+
"ThreadAt" $.Issue.IssueAt)
60
+
}}
61
+
{{ end }}
62
+
</div>
49
63
</section>
50
64
{{ end }}
51
65
···
76
90
>
77
91
<div class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-4 px-4 relative w-full md:w-3/5">
78
92
<div class="text-sm pb-2 text-gray-500 dark:text-gray-400">
79
-
{{ didOrHandle .LoggedInUser.Did .LoggedInUser.Handle }}
93
+
{{ template "user/fragments/picHandleLink" (didOrHandle .LoggedInUser.Did .LoggedInUser.Handle) }}
80
94
</div>
81
95
<textarea
82
96
id="comment-textarea"
+2
-2
appview/pages/templates/repo/issues/issues.html
+2
-2
appview/pages/templates/repo/issues/issues.html
···
27
27
</div>
28
28
<a
29
29
href="/{{ .RepoInfo.FullName }}/issues/new"
30
-
class="btn text-sm flex items-center justify-center gap-2 no-underline hover:no-underline"
30
+
class="btn-create text-sm flex items-center justify-center gap-2 no-underline hover:no-underline hover:text-white"
31
31
>
32
32
{{ i "circle-plus" "w-4 h-4" }}
33
33
<span>new</span>
···
66
66
67
67
<span class="ml-1">
68
68
{{ $owner := index $.DidHandleMap .OwnerDid }}
69
-
{{ template "user/fragments/picHandle" $owner }}
69
+
{{ template "user/fragments/picHandleLink" $owner }}
70
70
</span>
71
71
72
72
<span class="before:content-['ยท']">
+5
-4
appview/pages/templates/repo/issues/new.html
+5
-4
appview/pages/templates/repo/issues/new.html
···
23
23
></textarea>
24
24
</div>
25
25
<div>
26
-
<button type="submit" class="btn flex items-center gap-2">
27
-
create
28
-
<span id="spinner" class="group">
29
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
26
+
<button type="submit" class="btn-create flex items-center gap-2">
27
+
{{ i "circle-plus" "w-4 h-4" }}
28
+
create issue
29
+
<span id="create-pull-spinner" class="group">
30
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
30
31
</span>
31
32
</button>
32
33
</div>
+2
-2
appview/pages/templates/repo/log.html
+2
-2
appview/pages/templates/repo/log.html
···
31
31
<td class=" py-3 align-top">
32
32
{{ $didOrHandle := index $.EmailToDidOrHandle $commit.Author.Email }}
33
33
{{ if $didOrHandle }}
34
-
<a href="/{{ $didOrHandle }}" class="text-gray-700 dark:text-gray-300 no-underline hover:underline">{{ $didOrHandle }}</a>
34
+
{{ template "user/fragments/picHandleLink" $didOrHandle }}
35
35
{{ else }}
36
36
<a href="mailto:{{ $commit.Author.Email }}" class="text-gray-700 dark:text-gray-300 no-underline hover:underline">{{ $commit.Author.Name }}</a>
37
37
{{ end }}
···
159
159
{{ $didOrHandle := index $.EmailToDidOrHandle $commit.Author.Email }}
160
160
<a href="{{ if $didOrHandle }}/{{ $didOrHandle }}{{ else }}mailto:{{ $commit.Author.Email }}{{ end }}"
161
161
class="text-gray-500 dark:text-gray-400 no-underline hover:underline">
162
-
{{ if $didOrHandle }}{{ $didOrHandle }}{{ else }}{{ $commit.Author.Name }}{{ end }}
162
+
{{ if $didOrHandle }}{{ template "user/fragments/picHandleLink" $didOrHandle }}{{ else }}{{ $commit.Author.Name }}{{ end }}
163
163
</a>
164
164
</span>
165
165
<div class="inline-block px-1 select-none after:content-['ยท']"></div>
+8
-7
appview/pages/templates/repo/new.html
+8
-7
appview/pages/templates/repo/new.html
···
60
60
</fieldset>
61
61
62
62
<div class="space-y-2">
63
-
<button type="submit" class="btn flex items-center">
64
-
create repo
65
-
<span id="spinner" class="group">
66
-
{{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
67
-
</span>
68
-
</button>
69
-
<div id="repo" class="error"></div>
63
+
<button type="submit" class="btn-create flex items-center gap-2">
64
+
{{ i "book-plus" "w-4 h-4" }}
65
+
create repo
66
+
<span id="create-pull-spinner" class="group">
67
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
68
+
</span>
69
+
</button>
70
+
<div id="repo" class="error"></div>
70
71
</div>
71
72
</form>
72
73
</div>
+3
-4
appview/pages/templates/repo/pipelines/fragments/logBlock.html
+3
-4
appview/pages/templates/repo/pipelines/fragments/logBlock.html
···
1
1
{{ define "repo/pipelines/fragments/logBlock" }}
2
2
<div id="lines" hx-swap-oob="beforeend">
3
-
<details id="step-{{ .Id }}" {{if not .Collapsed}}open{{end}} class="group bg-gray-100 px-2 dark:bg-gray-900">
4
-
<summary class="sticky top-0 py-1 list-none cursor-pointer py-2 bg-gray-100 dark:bg-gray-900 hover:text-gray-500 hover:dark:text-gray-400">
3
+
<details id="step-{{ .Id }}" {{if not .Collapsed}}open{{end}} class="group bg-gray-100 pb-2 px-2 dark:bg-gray-900">
4
+
<summary class="sticky top-0 pt-2 group-open:pb-2 list-none cursor-pointer bg-gray-100 dark:bg-gray-900 hover:text-gray-500 hover:dark:text-gray-400">
5
5
<div class="group-open:hidden flex items-center gap-1">
6
6
{{ i "chevron-right" "w-4 h-4" }} {{ .Name }}
7
7
</div>
···
9
9
{{ i "chevron-down" "w-4 h-4" }} {{ .Name }}
10
10
</div>
11
11
</summary>
12
-
<div class="text-blue-600 dark:text-blue-300 font-mono">{{ .Command }}</div>
13
-
<div id="step-body-{{ .Id }}" class="font-mono"></div>
12
+
<div class="font-mono whitespace-pre overflow-x-auto"><div class="text-blue-600 dark:text-blue-300">{{ .Command }}</div><div id="step-body-{{ .Id }}"></div></div>
14
13
</details>
15
14
</div>
16
15
{{ end }}
+1
-3
appview/pages/templates/repo/pipelines/fragments/logLine.html
+1
-3
appview/pages/templates/repo/pipelines/fragments/logLine.html
+1
-1
appview/pages/templates/repo/pipelines/fragments/tooltip.html
+1
-1
appview/pages/templates/repo/pipelines/fragments/tooltip.html
+13
-8
appview/pages/templates/repo/pipelines/pipelines.html
+13
-8
appview/pages/templates/repo/pipelines/pipelines.html
···
34
34
{{ $p := index . 1 }}
35
35
{{ with $p }}
36
36
<div class="grid grid-cols-6 md:grid-cols-12 gap-2 items-center w-full">
37
-
<div class="col-span-2 md:col-span-8 flex items-center gap-4">
37
+
<div class="text-sm md:text-base col-span-1">
38
+
{{ .Trigger.Kind.String }}
39
+
</div>
40
+
41
+
<div class="col-span-2 md:col-span-7 flex items-center gap-4">
38
42
{{ $target := .Trigger.TargetRef }}
39
43
{{ $workflows := .Workflows }}
40
44
{{ $link := "" }}
···
43
47
{{ end }}
44
48
{{ if .Trigger.IsPush }}
45
49
<span class="font-bold">{{ $target }}</span>
46
-
<span>push</span>
47
50
<span class="hidden md:inline-flex gap-2 items-center font-mono text-sm">
48
51
{{ $old := deref .Trigger.PushOldSha }}
49
52
{{ $new := deref .Trigger.PushNewSha }}
···
53
56
<a href="/{{ $root.RepoInfo.FullName }}/commit/{{ $old }}">{{ slice $old 0 8 }}</a>
54
57
</span>
55
58
{{ else if .Trigger.IsPullRequest }}
56
-
<span>
57
-
pull request
58
-
<span class="inline-flex gap-2 items-center">
59
-
{{ $target }}
60
-
{{ i "arrow-left" "size-4" }}
61
-
{{ .Trigger.PRSourceBranch }}
59
+
{{ $sha := deref .Trigger.PRSourceSha }}
60
+
<span class="inline-flex gap-2 items-center">
61
+
<span class="font-bold">{{ $target }}</span>
62
+
{{ i "arrow-left" "size-4" }}
63
+
{{ .Trigger.PRSourceBranch }}
64
+
<span class="text-sm font-mono">
65
+
@
66
+
<a href="/{{ $root.RepoInfo.FullName }}/commit/{{ $sha }}">{{ slice $sha 0 8 }}</a>
62
67
</span>
63
68
</span>
64
69
{{ end }}
+17
-1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
+17
-1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
···
29
29
<span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1">
30
30
opened by
31
31
{{ $owner := index $.DidHandleMap .Pull.OwnerDid }}
32
-
{{ template "user/fragments/picHandle" $owner }}
32
+
{{ template "user/fragments/picHandleLink" $owner }}
33
33
<span class="select-none before:content-['\00B7']"></span>
34
34
<time>{{ .Pull.Created | timeFmt }}</time>
35
35
···
60
60
<article id="body" class="mt-8 prose dark:prose-invert">
61
61
{{ .Pull.Body | markdown }}
62
62
</article>
63
+
{{ end }}
64
+
65
+
{{ with .OrderedReactionKinds }}
66
+
<div class="flex items-center gap-2 mt-2">
67
+
{{ template "repo/fragments/reactionsPopUp" . }}
68
+
{{ range $kind := . }}
69
+
{{
70
+
template "repo/fragments/reaction"
71
+
(dict
72
+
"Kind" $kind
73
+
"Count" (index $.Reactions $kind)
74
+
"IsReacted" (index $.UserReacted $kind)
75
+
"ThreadAt" $.Pull.PullAt)
76
+
}}
77
+
{{ end }}
78
+
</div>
63
79
{{ end }}
64
80
</section>
65
81
+3
-4
appview/pages/templates/repo/pulls/fragments/pullNewComment.html
+3
-4
appview/pages/templates/repo/pulls/fragments/pullNewComment.html
···
1
1
{{ define "repo/pulls/fragments/pullNewComment" }}
2
-
<div
3
-
id="pull-comment-card-{{ .RoundNumber }}"
2
+
<div
3
+
id="pull-comment-card-{{ .RoundNumber }}"
4
4
class="bg-white dark:bg-gray-800 rounded drop-shadow-sm p-4 relative w-full flex flex-col gap-2">
5
5
<div class="text-sm text-gray-500 dark:text-gray-400">
6
-
{{ didOrHandle .LoggedInUser.Did .LoggedInUser.Handle }}
6
+
{{ template "user/fragments/picHandleLink" (didOrHandle .LoggedInUser.Did .LoggedInUser.Handle) }}
7
7
</div>
8
8
<form
9
9
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ .RoundNumber }}/comment"
···
38
38
</form>
39
39
</div>
40
40
{{ end }}
41
-
+3
-2
appview/pages/templates/repo/pulls/fragments/pullStack.html
+3
-2
appview/pages/templates/repo/pulls/fragments/pullStack.html
···
10
10
{{ i "chevrons-down-up" "w-4 h-4" }}
11
11
</span>
12
12
STACK
13
-
<span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 text-sm ml-1">{{ len .Stack }}</span>
13
+
<span class="bg-gray-200 dark:bg-gray-700 font-normal rounded py-1/2 px-1 text-sm">{{ len .Stack }}</span>
14
14
</span>
15
15
</summary>
16
16
{{ block "pullList" (list .Stack $) }} {{ end }}
···
41
41
<div class="grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700">
42
42
{{ range $pull := $list }}
43
43
{{ $isCurrent := false }}
44
+
{{ $pipeline := index $root.Pipelines $pull.LatestSha }}
44
45
{{ with $root.Pull }}
45
46
{{ $isCurrent = eq $pull.PullId $root.Pull.PullId }}
46
47
{{ end }}
···
52
53
</div>
53
54
{{ end }}
54
55
<div class="{{ if not $isCurrent }} pl-6 {{ end }} flex-grow min-w-0 w-full py-2">
55
-
{{ template "repo/pulls/fragments/summarizedHeader" $pull }}
56
+
{{ template "repo/pulls/fragments/summarizedHeader" (list $pull $pipeline) }}
56
57
</div>
57
58
</div>
58
59
</a>
+36
-26
appview/pages/templates/repo/pulls/fragments/summarizedPullHeader.html
+36
-26
appview/pages/templates/repo/pulls/fragments/summarizedPullHeader.html
···
1
1
{{ define "repo/pulls/fragments/summarizedHeader" }}
2
-
<div class="flex text-sm items-center justify-between w-full">
3
-
<div class="flex items-center gap-2 min-w-0 flex-1 pr-2">
4
-
<div class="flex-shrink-0">
5
-
{{ template "repo/pulls/fragments/summarizedPullState" .State }}
2
+
{{ $pull := index . 0 }}
3
+
{{ $pipeline := index . 1 }}
4
+
{{ with $pull }}
5
+
<div class="flex text-sm items-center justify-between w-full">
6
+
<div class="flex items-center gap-2 min-w-0 flex-1 pr-2">
7
+
<div class="flex-shrink-0">
8
+
{{ template "repo/pulls/fragments/summarizedPullState" .State }}
9
+
</div>
10
+
<span class="truncate text-sm text-gray-800 dark:text-gray-200">
11
+
<span class="text-gray-500 dark:text-gray-400">#{{ .PullId }}</span>
12
+
{{ .Title }}
13
+
</span>
6
14
</div>
7
-
<span class="truncate text-sm text-gray-800 dark:text-gray-200">
8
-
<span class="text-gray-500 dark:text-gray-400">#{{ .PullId }}</span>
9
-
{{ .Title }}
10
-
</span>
11
-
</div>
12
15
13
-
<div class="flex-shrink-0">
14
-
{{ $latestRound := .LastRoundNumber }}
15
-
{{ $lastSubmission := index .Submissions $latestRound }}
16
-
{{ $commentCount := len $lastSubmission.Comments }}
17
-
<span>
18
-
<div class="inline-flex items-center gap-2">
19
-
{{ i "message-square" "w-3 h-3 md:hidden" }}
20
-
{{ $commentCount }}
21
-
<span class="hidden md:inline">comment{{if ne $commentCount 1}}s{{end}}</span>
22
-
</div>
23
-
</span>
24
-
<span class="mx-2 before:content-['ยท'] before:select-none"></span>
25
-
<span>
26
-
<span class="hidden md:inline">round</span>
27
-
<span class="font-mono">#{{ $latestRound }}</span>
28
-
</span>
16
+
<div class="flex-shrink-0 flex items-center">
17
+
{{ $latestRound := .LastRoundNumber }}
18
+
{{ $lastSubmission := index .Submissions $latestRound }}
19
+
{{ $commentCount := len $lastSubmission.Comments }}
20
+
{{ if $pipeline }}
21
+
<div class="inline-flex items-center gap-2">
22
+
{{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }}
23
+
<span class="mx-2 before:content-['ยท'] before:select-none"></span>
24
+
</div>
25
+
{{ end }}
26
+
<span>
27
+
<div class="inline-flex items-center gap-2">
28
+
{{ i "message-square" "w-3 h-3 md:hidden" }}
29
+
{{ $commentCount }}
30
+
<span class="hidden md:inline">comment{{if ne $commentCount 1}}s{{end}}</span>
31
+
</div>
32
+
</span>
33
+
<span class="mx-2 before:content-['ยท'] before:select-none"></span>
34
+
<span>
35
+
<span class="hidden md:inline">round</span>
36
+
<span class="font-mono">#{{ $latestRound }}</span>
37
+
</span>
38
+
</div>
29
39
</div>
30
-
</div>
40
+
{{ end }}
31
41
{{ end }}
32
42
+1
-1
appview/pages/templates/repo/pulls/new.html
+1
-1
appview/pages/templates/repo/pulls/new.html
···
141
141
</div>
142
142
143
143
<div class="flex justify-start items-center gap-2 mt-4">
144
-
<button type="submit" class="btn flex items-center gap-2">
144
+
<button type="submit" class="btn-create flex items-center gap-2">
145
145
{{ i "git-pull-request-create" "w-4 h-4" }}
146
146
create pull
147
147
<span id="create-pull-spinner" class="group">
+47
-8
appview/pages/templates/repo/pulls/pull.html
+47
-8
appview/pages/templates/repo/pulls/pull.html
···
5
5
{{ define "extrameta" }}
6
6
{{ $title := printf "%s · pull #%d · %s" .Pull.Title .Pull.PullId .RepoInfo.FullName }}
7
7
{{ $url := printf "https://tangled.sh/%s/pulls/%d" .RepoInfo.FullName .Pull.PullId }}
8
-
8
+
9
9
{{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }}
10
10
{{ end }}
11
11
···
46
46
</div>
47
47
<!-- round summary -->
48
48
<div class="rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400">
49
-
<span>
49
+
<span class="gap-1 flex items-center">
50
50
{{ $owner := index $.DidHandleMap $.Pull.OwnerDid }}
51
51
{{ $re := "re" }}
52
52
{{ if eq .RoundNumber 0 }}
53
53
{{ $re = "" }}
54
54
{{ end }}
55
55
<span class="hidden md:inline">{{$re}}submitted</span>
56
-
by <a href="/{{ $owner }}">{{ $owner }}</a>
56
+
by {{ template "user/fragments/picHandleLink" $owner }}
57
57
<span class="select-none before:content-['\00B7']"></span>
58
58
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500" href="#round-#{{ .RoundNumber }}"><time>{{ .Created | shortTimeFmt }}</time></a>
59
59
<span class="select-none before:content-['ยท']"></span>
···
68
68
<a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group"
69
69
hx-boost="true"
70
70
href="/{{ $.RepoInfo.FullName }}/pulls/{{ $.Pull.PullId }}/round/{{.RoundNumber}}">
71
-
{{ i "file-diff" "w-4 h-4" }}
71
+
{{ i "file-diff" "w-4 h-4" }}
72
72
<span class="hidden md:inline">diff</span>
73
73
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
74
74
</a>
···
150
150
{{ if gt $cidx 0 }}
151
151
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
152
152
{{ end }}
153
-
<div class="text-sm text-gray-500 dark:text-gray-400">
154
-
{{ $owner := index $.DidHandleMap $c.OwnerDid }}
155
-
<a href="/{{$owner}}">{{$owner}}</a>
153
+
<div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1">
154
+
{{ $owner := index $.DidHandleMap $c.OwnerDid }}
155
+
{{ template "user/fragments/picHandleLink" $owner }}
156
156
<span class="before:content-['ยท']"></span>
157
157
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}"><time>{{ $c.Created | shortTimeFmt }}</time></a>
158
158
</div>
···
162
162
</div>
163
163
{{ end }}
164
164
165
+
{{ block "pipelineStatus" (list $ .) }} {{ end }}
166
+
165
167
{{ if eq $lastIdx .RoundNumber }}
166
168
{{ block "mergeStatus" $ }} {{ end }}
167
169
{{ block "resubmitStatus" $ }} {{ end }}
···
260
262
{{ end }}
261
263
{{ end }}
262
264
263
-
{{ define "commits" }}
265
+
{{ define "pipelineStatus" }}
266
+
{{ $root := index . 0 }}
267
+
{{ $submission := index . 1 }}
268
+
{{ $pipeline := index $root.Pipelines $submission.SourceRev }}
269
+
{{ with $pipeline }}
270
+
{{ $id := .Id }}
271
+
{{ if .Statuses }}
272
+
<div class="max-w-80 grid grid-cols-1 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700">
273
+
{{ range $name, $all := .Statuses }}
274
+
<a href="/{{ $root.RepoInfo.FullName }}/pipelines/{{ $id }}/workflow/{{ $name }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25">
275
+
<div
276
+
class="flex gap-2 items-center justify-between p-2">
277
+
{{ $lastStatus := $all.Latest }}
278
+
{{ $kind := $lastStatus.Status.String }}
279
+
280
+
{{ $t := .TimeTaken }}
281
+
{{ $time := "" }}
282
+
283
+
{{ if $t }}
284
+
{{ $time = durationFmt $t }}
285
+
{{ else }}
286
+
{{ $time = printf "%s ago" (shortTimeFmt $lastStatus.Created) }}
287
+
{{ end }}
288
+
289
+
<div id="left" class="flex items-center gap-2 flex-shrink-0">
290
+
{{ template "repo/pipelines/fragments/workflowSymbol" $all }}
291
+
{{ $name }}
292
+
</div>
293
+
<div id="right" class="flex items-center gap-2 flex-shrink-0">
294
+
<span class="font-bold">{{ $kind }}</span>
295
+
<time>{{ $time }}</time>
296
+
</div>
297
+
</div>
298
+
</a>
299
+
{{ end }}
300
+
</div>
301
+
{{ end }}
302
+
{{ end }}
264
303
{{ end }}
+3
-3
appview/pages/templates/repo/pulls/pulls.html
+3
-3
appview/pages/templates/repo/pulls/pulls.html
···
34
34
</div>
35
35
<a
36
36
href="/{{ .RepoInfo.FullName }}/pulls/new"
37
-
class="btn text-sm flex items-center gap-2 no-underline hover:no-underline"
37
+
class="btn-create text-sm flex items-center gap-2 no-underline hover:no-underline hover:text-white"
38
38
>
39
39
{{ i "git-pull-request-create" "w-4 h-4" }}
40
40
<span>new</span>
···
76
76
</span>
77
77
78
78
<span class="ml-1">
79
-
{{ template "user/fragments/picHandle" $owner }}
79
+
{{ template "user/fragments/picHandleLink" $owner }}
80
80
</span>
81
81
82
82
<span>
···
156
156
<a href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $pull.PullId }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25">
157
157
<div class="flex gap-2 items-center px-6">
158
158
<div class="flex-grow min-w-0 w-full py-2">
159
-
{{ template "repo/pulls/fragments/summarizedHeader" $pull }}
159
+
{{ template "repo/pulls/fragments/summarizedHeader" (list $pull 0) }}
160
160
</div>
161
161
</div>
162
162
</a>
+7
-1
appview/pages/templates/repo/tree.html
+7
-1
appview/pages/templates/repo/tree.html
···
11
11
{{ template "repo/fragments/meta" . }}
12
12
{{ $title := printf "%s at %s · %s" $path .Ref .RepoInfo.FullName }}
13
13
{{ $url := printf "https://tangled.sh/%s/tree/%s%s" .RepoInfo.FullName .Ref $path }}
14
-
14
+
15
15
{{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }}
16
16
{{ end }}
17
17
···
63
63
</div>
64
64
</a>
65
65
{{ if .LastCommit}}
66
+
<div class="flex items-end gap-2">
67
+
<span class="text text-gray-500 dark:text-gray-400 mr-6">{{ .LastCommit.Message }}</span>
66
68
<time class="text-xs text-gray-500 dark:text-gray-400">{{ timeFmt .LastCommit.When }}</time>
69
+
</div>
67
70
{{ end }}
68
71
</div>
69
72
</div>
···
80
83
</div>
81
84
</a>
82
85
{{ if .LastCommit}}
86
+
<div class="flex items-end gap-2">
87
+
<span class="text text-gray-500 dark:text-gray-400 mr-6">{{ .LastCommit.Message }}</span>
83
88
<time class="text-xs text-gray-500 dark:text-gray-400">{{ timeFmt .LastCommit.When }}</time>
89
+
</div>
84
90
{{ end }}
85
91
</div>
86
92
</div>
+5
-5
appview/pages/templates/timeline.html
+5
-5
appview/pages/templates/timeline.html
···
61
61
{{ $userHandle := index $.DidHandleMap .Repo.Did }}
62
62
<div class="flex items-center">
63
63
<p class="text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2">
64
-
{{ template "user/fragments/picHandle" $userHandle }}
64
+
{{ template "user/fragments/picHandleLink" $userHandle }}
65
65
{{ if .Source }}
66
66
forked
67
67
<a
···
95
95
{{ $subjectHandle := index $.DidHandleMap .Follow.SubjectDid }}
96
96
<div class="flex items-center">
97
97
<p class="text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2">
98
-
{{ template "user/fragments/picHandle" $userHandle }}
98
+
{{ template "user/fragments/picHandleLink" $userHandle }}
99
99
followed
100
-
{{ template "user/fragments/picHandle" $subjectHandle }}
100
+
{{ template "user/fragments/picHandleLink" $subjectHandle }}
101
101
<time
102
102
class="text-gray-700 dark:text-gray-400 text-xs"
103
103
>{{ .Follow.FollowedAt | timeFmt }}</time
···
108
108
{{ $starrerHandle := index $.DidHandleMap .Star.StarredByDid }}
109
109
{{ $repoOwnerHandle := index $.DidHandleMap .Star.Repo.Did }}
110
110
<div class="flex items-center">
111
-
{{ template "user/fragments/picHandle" $starrerHandle }}
112
-
<p class="text-gray-600 dark:text-gray-300 flex items-center gap-2">
111
+
<p class="text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2">
112
+
{{ template "user/fragments/picHandleLink" $starrerHandle }}
113
113
starred
114
114
<a
115
115
href="/{{ $repoOwnerHandle }}/{{ .Star.Repo.Name }}"
+6
-8
appview/pages/templates/user/fragments/picHandle.html
+6
-8
appview/pages/templates/user/fragments/picHandle.html
···
1
1
{{ define "user/fragments/picHandle" }}
2
-
<a href="/{{ . }}" class="flex items-center">
3
-
<img
4
-
src="{{ tinyAvatar . }}"
5
-
alt="{{ . }}"
6
-
class="rounded-full h-6 w-6 mr-1 border border-gray-300 dark:border-gray-700"
7
-
/>
8
-
{{ . | truncateAt30 }}
9
-
</a>
2
+
<img
3
+
src="{{ tinyAvatar . }}"
4
+
alt="{{ . }}"
5
+
class="rounded-full h-6 w-6 mr-1 border border-gray-300 dark:border-gray-700"
6
+
/>
7
+
{{ . | truncateAt30 }}
10
8
{{ end }}
+5
appview/pages/templates/user/fragments/picHandleLink.html
+5
appview/pages/templates/user/fragments/picHandleLink.html
+69
-12
appview/pulls/pulls.go
+69
-12
appview/pulls/pulls.go
···
167
167
resubmitResult = s.resubmitCheck(f, pull, stack)
168
168
}
169
169
170
+
repoInfo := f.RepoInfo(user)
171
+
172
+
m := make(map[string]db.Pipeline)
173
+
174
+
var shas []string
175
+
for _, s := range pull.Submissions {
176
+
shas = append(shas, s.SourceRev)
177
+
}
178
+
for _, p := range stack {
179
+
shas = append(shas, p.LatestSha())
180
+
}
181
+
for _, p := range abandonedPulls {
182
+
shas = append(shas, p.LatestSha())
183
+
}
184
+
185
+
ps, err := db.GetPipelineStatuses(
186
+
s.db,
187
+
db.FilterEq("repo_owner", repoInfo.OwnerDid),
188
+
db.FilterEq("repo_name", repoInfo.Name),
189
+
db.FilterEq("knot", repoInfo.Knot),
190
+
db.FilterIn("sha", shas),
191
+
)
192
+
if err != nil {
193
+
log.Printf("failed to fetch pipeline statuses: %s", err)
194
+
// non-fatal
195
+
}
196
+
197
+
for _, p := range ps {
198
+
m[p.Sha] = p
199
+
}
200
+
201
+
reactionCountMap, err := db.GetReactionCountMap(s.db, pull.PullAt())
202
+
if err != nil {
203
+
log.Println("failed to get pull reactions")
204
+
s.pages.Notice(w, "pulls", "Failed to load pull. Try again later.")
205
+
}
206
+
207
+
userReactions := map[db.ReactionKind]bool{}
208
+
if user != nil {
209
+
userReactions = db.GetReactionStatusMap(s.db, user.Did, pull.PullAt())
210
+
}
211
+
170
212
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
171
213
LoggedInUser: user,
172
-
RepoInfo: f.RepoInfo(user),
214
+
RepoInfo: repoInfo,
173
215
DidHandleMap: didHandleMap,
174
216
Pull: pull,
175
217
Stack: stack,
176
218
AbandonedPulls: abandonedPulls,
177
219
MergeCheck: mergeCheckResponse,
178
220
ResubmitCheck: resubmitResult,
221
+
Pipelines: m,
222
+
223
+
OrderedReactionKinds: db.OrderedReactionKinds,
224
+
Reactions: reactionCountMap,
225
+
UserReacted: userReactions,
179
226
})
180
227
}
181
228
···
798
845
sourceBranch string,
799
846
isStacked bool,
800
847
) {
801
-
pullSource := &db.PullSource{
802
-
Branch: sourceBranch,
803
-
}
804
-
recordPullSource := &tangled.RepoPull_Source{
805
-
Branch: sourceBranch,
806
-
}
807
-
808
848
// Generate a patch using /compare
809
849
ksClient, err := knotclient.NewUnsignedClient(f.Knot, s.config.Core.Dev)
810
850
if err != nil {
···
828
868
return
829
869
}
830
870
871
+
pullSource := &db.PullSource{
872
+
Branch: sourceBranch,
873
+
}
874
+
recordPullSource := &tangled.RepoPull_Source{
875
+
Branch: sourceBranch,
876
+
Sha: comparison.Rev2,
877
+
}
878
+
831
879
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource, isStacked)
832
880
}
833
881
···
914
962
return
915
963
}
916
964
917
-
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, &db.PullSource{
965
+
pullSource := &db.PullSource{
918
966
Branch: sourceBranch,
919
967
RepoAt: &forkAtUri,
920
-
}, &tangled.RepoPull_Source{Branch: sourceBranch, Repo: &fork.AtUri}, isStacked)
968
+
}
969
+
recordPullSource := &tangled.RepoPull_Source{
970
+
Branch: sourceBranch,
971
+
Repo: &fork.AtUri,
972
+
Sha: sourceRev,
973
+
}
974
+
975
+
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource, isStacked)
921
976
}
922
977
923
978
func (s *Pulls) createPullRequest(
···
934
989
) {
935
990
if isStacked {
936
991
// creates a series of PRs, each linking to the previous, identified by jj's change-id
937
-
s.createStackedPulLRequest(
992
+
s.createStackedPullRequest(
938
993
w,
939
994
r,
940
995
f,
···
1049
1104
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId))
1050
1105
}
1051
1106
1052
-
func (s *Pulls) createStackedPulLRequest(
1107
+
func (s *Pulls) createStackedPullRequest(
1053
1108
w http.ResponseWriter,
1054
1109
r *http.Request,
1055
1110
f *reporesolver.ResolvedRepo,
···
1566
1621
if pull.IsBranchBased() {
1567
1622
recordPullSource = &tangled.RepoPull_Source{
1568
1623
Branch: pull.PullSource.Branch,
1624
+
Sha: sourceRev,
1569
1625
}
1570
1626
}
1571
1627
if pull.IsForkBased() {
···
1573
1629
recordPullSource = &tangled.RepoPull_Source{
1574
1630
Branch: pull.PullSource.Branch,
1575
1631
Repo: &repoAt,
1632
+
Sha: sourceRev,
1576
1633
}
1577
1634
}
1578
1635
+1
-1
appview/repo/index.go
+1
-1
appview/repo/index.go
···
133
133
for _, c := range commitsTrunc {
134
134
shas = append(shas, c.Hash.String())
135
135
}
136
-
pipelines, err := rp.getPipelineStatuses(repoInfo, shas)
136
+
pipelines, err := getPipelineStatuses(rp.db, repoInfo, shas)
137
137
if err != nil {
138
138
log.Printf("failed to fetch pipeline statuses: %s", err)
139
139
// non-fatal
+2
-2
appview/repo/repo.go
+2
-2
appview/repo/repo.go
···
139
139
for _, c := range repolog.Commits {
140
140
shas = append(shas, c.Hash.String())
141
141
}
142
-
pipelines, err := rp.getPipelineStatuses(repoInfo, shas)
142
+
pipelines, err := getPipelineStatuses(rp.db, repoInfo, shas)
143
143
if err != nil {
144
144
log.Println(err)
145
145
// non-fatal
···
304
304
305
305
user := rp.oauth.GetUser(r)
306
306
repoInfo := f.RepoInfo(user)
307
-
pipelines, err := rp.getPipelineStatuses(repoInfo, []string{result.Diff.Commit.This})
307
+
pipelines, err := getPipelineStatuses(rp.db, repoInfo, []string{result.Diff.Commit.This})
308
308
if err != nil {
309
309
log.Println(err)
310
310
// non-fatal
+3
-2
appview/repo/repo_util.go
+3
-2
appview/repo/repo_util.go
···
105
105
// grab pipelines from DB and munge that into a hashmap with commit sha as key
106
106
//
107
107
// golang is so blessed that it requires 35 lines of imperative code for this
108
-
func (rp *Repo) getPipelineStatuses(
108
+
func getPipelineStatuses(
109
+
d *db.DB,
109
110
repoInfo repoinfo.RepoInfo,
110
111
shas []string,
111
112
) (map[string]db.Pipeline, error) {
···
116
117
}
117
118
118
119
ps, err := db.GetPipelineStatuses(
119
-
rp.db,
120
+
d,
120
121
db.FilterEq("repo_owner", repoInfo.OwnerDid),
121
122
db.FilterEq("repo_name", repoInfo.Name),
122
123
db.FilterEq("knot", repoInfo.Knot),
+7
-8
appview/state/knotstream.go
+7
-8
appview/state/knotstream.go
···
143
143
// trigger info
144
144
var trigger db.Trigger
145
145
var sha string
146
-
switch record.TriggerMetadata.Kind {
146
+
trigger.Kind = workflow.TriggerKind(record.TriggerMetadata.Kind)
147
+
switch trigger.Kind {
147
148
case workflow.TriggerKindPush:
148
-
trigger.Kind = workflow.TriggerKindPush
149
149
trigger.PushRef = &record.TriggerMetadata.Push.Ref
150
150
trigger.PushNewSha = &record.TriggerMetadata.Push.NewSha
151
151
trigger.PushOldSha = &record.TriggerMetadata.Push.OldSha
152
152
sha = *trigger.PushNewSha
153
153
case workflow.TriggerKindPullRequest:
154
-
trigger.Kind = workflow.TriggerKindPush
155
154
trigger.PRSourceBranch = &record.TriggerMetadata.PullRequest.SourceBranch
156
155
trigger.PRTargetBranch = &record.TriggerMetadata.PullRequest.TargetBranch
157
156
trigger.PRSourceSha = &record.TriggerMetadata.PullRequest.SourceSha
···
161
160
162
161
tx, err := d.Begin()
163
162
if err != nil {
164
-
return err
163
+
return fmt.Errorf("failed to start txn: %w", err)
165
164
}
166
165
167
166
triggerId, err := db.AddTrigger(tx, trigger)
168
167
if err != nil {
169
-
return err
168
+
return fmt.Errorf("failed to add trigger entry: %w", err)
170
169
}
171
170
172
171
pipeline := db.Pipeline{
···
180
179
181
180
err = db.AddPipeline(tx, pipeline)
182
181
if err != nil {
183
-
return err
182
+
return fmt.Errorf("failed to add pipeline: %w", err)
184
183
}
185
184
186
185
err = tx.Commit()
187
186
if err != nil {
188
-
return err
187
+
return fmt.Errorf("failed to commit txn: %w", err)
189
188
}
190
189
191
-
return err
190
+
return nil
192
191
}
+126
appview/state/reaction.go
+126
appview/state/reaction.go
···
1
+
package state
2
+
3
+
import (
4
+
"log"
5
+
"net/http"
6
+
"time"
7
+
8
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
+
"github.com/bluesky-social/indigo/atproto/syntax"
10
+
11
+
lexutil "github.com/bluesky-social/indigo/lex/util"
12
+
"tangled.sh/tangled.sh/core/api/tangled"
13
+
"tangled.sh/tangled.sh/core/appview"
14
+
"tangled.sh/tangled.sh/core/appview/db"
15
+
"tangled.sh/tangled.sh/core/appview/pages"
16
+
)
17
+
18
+
func (s *State) React(w http.ResponseWriter, r *http.Request) {
19
+
currentUser := s.oauth.GetUser(r)
20
+
21
+
subject := r.URL.Query().Get("subject")
22
+
if subject == "" {
23
+
log.Println("invalid form")
24
+
return
25
+
}
26
+
27
+
subjectUri, err := syntax.ParseATURI(subject)
28
+
if err != nil {
29
+
log.Println("invalid form")
30
+
return
31
+
}
32
+
33
+
reactionKind, ok := db.ParseReactionKind(r.URL.Query().Get("kind"))
34
+
if !ok {
35
+
log.Println("invalid reaction kind")
36
+
return
37
+
}
38
+
39
+
client, err := s.oauth.AuthorizedClient(r)
40
+
if err != nil {
41
+
log.Println("failed to authorize client", err)
42
+
return
43
+
}
44
+
45
+
switch r.Method {
46
+
case http.MethodPost:
47
+
createdAt := time.Now().Format(time.RFC3339)
48
+
rkey := appview.TID()
49
+
resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
50
+
Collection: tangled.FeedReactionNSID,
51
+
Repo: currentUser.Did,
52
+
Rkey: rkey,
53
+
Record: &lexutil.LexiconTypeDecoder{
54
+
Val: &tangled.FeedReaction{
55
+
Subject: subjectUri.String(),
56
+
Reaction: reactionKind.String(),
57
+
CreatedAt: createdAt,
58
+
},
59
+
},
60
+
})
61
+
if err != nil {
62
+
log.Println("failed to create atproto record", err)
63
+
return
64
+
}
65
+
66
+
err = db.AddReaction(s.db, currentUser.Did, subjectUri, reactionKind, rkey)
67
+
if err != nil {
68
+
log.Println("failed to react", err)
69
+
return
70
+
}
71
+
72
+
count, err := db.GetReactionCount(s.db, subjectUri, reactionKind)
73
+
if err != nil {
74
+
log.Println("failed to get reaction count for ", subjectUri)
75
+
}
76
+
77
+
log.Println("created atproto record: ", resp.Uri)
78
+
79
+
s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
80
+
ThreadAt: subjectUri,
81
+
Kind: reactionKind,
82
+
Count: count,
83
+
IsReacted: true,
84
+
})
85
+
86
+
return
87
+
case http.MethodDelete:
88
+
reaction, err := db.GetReaction(s.db, currentUser.Did, subjectUri, reactionKind)
89
+
if err != nil {
90
+
log.Println("failed to get reaction relationship for", currentUser.Did, subjectUri)
91
+
return
92
+
}
93
+
94
+
_, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{
95
+
Collection: tangled.FeedReactionNSID,
96
+
Repo: currentUser.Did,
97
+
Rkey: reaction.Rkey,
98
+
})
99
+
100
+
if err != nil {
101
+
log.Println("failed to remove reaction")
102
+
return
103
+
}
104
+
105
+
err = db.DeleteReactionByRkey(s.db, currentUser.Did, reaction.Rkey)
106
+
if err != nil {
107
+
log.Println("failed to delete reaction from DB")
108
+
// this is not an issue, the firehose event might have already done this
109
+
}
110
+
111
+
count, err := db.GetReactionCount(s.db, subjectUri, reactionKind)
112
+
if err != nil {
113
+
log.Println("failed to get reaction count for ", subjectUri)
114
+
return
115
+
}
116
+
117
+
s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
118
+
ThreadAt: subjectUri,
119
+
Kind: reactionKind,
120
+
Count: count,
121
+
IsReacted: false,
122
+
})
123
+
124
+
return
125
+
}
126
+
}
+5
appview/state/router.go
+5
appview/state/router.go
···
137
137
r.Delete("/", s.Star)
138
138
})
139
139
140
+
r.With(middleware.AuthMiddleware(s.oauth)).Route("/react", func(r chi.Router) {
141
+
r.Post("/", s.React)
142
+
r.Delete("/", s.React)
143
+
})
144
+
140
145
r.Route("/profile", func(r chi.Router) {
141
146
r.Use(middleware.AuthMiddleware(s.oauth))
142
147
r.Get("/edit-bio", s.EditBioFragment)
+7
-1
appview/state/spindlestream.go
+7
-1
appview/state/spindlestream.go
···
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
+
"fmt"
6
7
"log/slog"
7
8
"strings"
8
9
"time"
···
100
101
ExitCode: exitCode,
101
102
}
102
103
103
-
return db.AddPipelineStatus(d, status)
104
+
err = db.AddPipelineStatus(d, status)
105
+
if err != nil {
106
+
return fmt.Errorf("failed to add pipeline status: %w", err)
107
+
}
108
+
109
+
return nil
104
110
}
+3
-4
cmd/gen.go
+3
-4
cmd/gen.go
···
15
15
"api/tangled/cbor_gen.go",
16
16
"tangled",
17
17
tangled.ActorProfile{},
18
+
tangled.FeedReaction{},
18
19
tangled.FeedStar{},
19
20
tangled.GitRefUpdate{},
20
21
tangled.GitRefUpdate_Meta{},
···
24
25
tangled.KnotMember{},
25
26
tangled.Pipeline{},
26
27
tangled.Pipeline_CloneOpts{},
27
-
tangled.Pipeline_Dependencies_Elem{},
28
+
tangled.Pipeline_Dependency{},
28
29
tangled.Pipeline_ManualTriggerData{},
29
-
tangled.Pipeline_ManualTriggerData_Inputs_Elem{},
30
+
tangled.Pipeline_Pair{},
30
31
tangled.Pipeline_PullRequestTriggerData{},
31
32
tangled.Pipeline_PushTriggerData{},
32
-
tangled.Pipeline_Step_Environment_Elem{},
33
33
tangled.PipelineStatus{},
34
34
tangled.Pipeline_Step{},
35
35
tangled.Pipeline_TriggerMetadata{},
36
36
tangled.Pipeline_TriggerRepo{},
37
37
tangled.Pipeline_Workflow{},
38
-
tangled.Pipeline_Workflow_Environment_Elem{},
39
38
tangled.PublicKey{},
40
39
tangled.Repo{},
41
40
tangled.RepoArtifact{},
+5
-4
docs/hacking.md
+5
-4
docs/hacking.md
···
47
47
`nixos-shell` like so:
48
48
49
49
```bash
50
-
QEMU_NET_OPTS="hostfwd=tcp::6000-:6000,hostfwd=tcp::2222-:22" nixos-shell --flake .#knotVM
50
+
nix run .#vm
51
+
# or nixos-shell --flake .#vm
51
52
52
53
# hit Ctrl-a + c + q to exit the VM
53
54
```
54
55
55
-
This starts a knot on port 6000 with `ssh` exposed on port
56
-
2222. You can push repositories to this VM with this ssh
57
-
config block on your main machine:
56
+
This starts a knot on port 6000, a spindle on port 6555
57
+
with `ssh` exposed on port 2222. You can push repositories
58
+
to this VM with this ssh config block on your main machine:
58
59
59
60
```bash
60
61
Host nixos-shell
+33
docs/knot-hosting.md
+33
docs/knot-hosting.md
···
89
89
systemctl start knotserver
90
90
```
91
91
92
+
The last step is to configure a reverse proxy like Nginx or Caddy to front yourself
93
+
knot. Here's an example configuration for Nginx:
94
+
95
+
```
96
+
server {
97
+
listen 80;
98
+
listen [::]:80;
99
+
server_name knot.example.com;
100
+
101
+
location / {
102
+
proxy_pass http://localhost:5555;
103
+
proxy_set_header Host $host;
104
+
proxy_set_header X-Real-IP $remote_addr;
105
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
106
+
proxy_set_header X-Forwarded-Proto $scheme;
107
+
}
108
+
109
+
# wss endpoint for git events
110
+
location /events {
111
+
proxy_set_header X-Forwarded-For $remote_addr;
112
+
proxy_set_header Host $http_host;
113
+
proxy_set_header Upgrade websocket;
114
+
proxy_set_header Connection Upgrade;
115
+
proxy_pass http://localhost:5555;
116
+
}
117
+
# additional config for SSL/TLS go here.
118
+
}
119
+
120
+
```
121
+
122
+
Remember to use Let's Encrypt or similar to procure a certificate for your
123
+
knot domain.
124
+
92
125
You should now have a running knot server! You can finalize your registration by hitting the
93
126
`initialize` button on the [/knots](/knots) page.
94
127
+12
-3
docs/spindle/hosting.md
+12
-3
docs/spindle/hosting.md
···
31
31
2. **Build the Spindle binary.**
32
32
33
33
```shell
34
-
go build -o spindle core/spindle/server.go
34
+
cd core
35
+
go mod download
36
+
go build -o cmd/spindle/spindle cmd/spindle/main.go
37
+
```
38
+
39
+
3. **Create the log directory.**
40
+
41
+
```shell
42
+
sudo mkdir -p /var/log/spindle
43
+
sudo chown $USER:$USER -R /var/log/spindle
35
44
```
36
45
37
-
3. **Run the Spindle binary.**
46
+
4. **Run the Spindle binary.**
38
47
39
48
```shell
40
-
./spindle
49
+
./cmd/spindle/spindle
41
50
```
42
51
43
52
Spindle will now start, connect to the Jetstream server, and begin processing pipelines.
+12
-1
eventconsumer/consumer.go
+12
-1
eventconsumer/consumer.go
···
172
172
func (c *Consumer) startConnectionLoop(ctx context.Context, source Source) {
173
173
defer c.wg.Done()
174
174
175
+
// attempt connection initially
176
+
err := c.runConnection(ctx, source)
177
+
if err != nil {
178
+
c.logger.Error("failed to run connection", "err", err)
179
+
}
180
+
181
+
timer := time.NewTimer(1 * time.Minute)
182
+
defer timer.Stop()
183
+
184
+
// every subsequent attempt is delayed by 1 minute
175
185
for {
176
186
select {
177
187
case <-ctx.Done():
178
188
return
179
-
default:
189
+
case <-timer.C:
180
190
err := c.runConnection(ctx, source)
181
191
if err != nil {
182
192
c.logger.Error("failed to run connection", "err", err)
183
193
}
194
+
timer.Reset(1 * time.Minute)
184
195
}
185
196
}
186
197
}
+47
-50
flake.nix
+47
-50
flake.nix
···
53
53
}: let
54
54
supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"];
55
55
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
56
-
nixpkgsFor = forAllSystems (system:
57
-
import nixpkgs {
58
-
inherit system;
59
-
overlays = [self.overlays.default];
60
-
});
56
+
nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
61
57
inherit (gitignore.lib) gitignoreSource;
62
-
in {
63
-
overlays.default = final: prev: let
58
+
mkPackageSet = pkgs: let
64
59
goModHash = "sha256-SLi+nALwCd/Lzn3aljwPqCo2UaM9hl/4OAjcHQLt2Bk=";
65
-
appviewDeps = {
66
-
inherit htmx-src htmx-ws-src lucide-src inter-fonts-src ibm-plex-mono-src goModHash gitignoreSource;
60
+
sqlite-lib = pkgs.callPackage ./nix/pkgs/sqlite-lib.nix {
61
+
inherit (pkgs) gcc;
62
+
inherit sqlite-lib-src;
67
63
};
68
-
knotDeps = {
69
-
inherit goModHash gitignoreSource;
64
+
genjwks = pkgs.callPackage ./nix/pkgs/genjwks.nix {inherit goModHash gitignoreSource;};
65
+
lexgen = pkgs.callPackage ./nix/pkgs/lexgen.nix {inherit indigo;};
66
+
appview = pkgs.callPackage ./nix/pkgs/appview.nix {
67
+
inherit sqlite-lib htmx-src htmx-ws-src lucide-src inter-fonts-src ibm-plex-mono-src goModHash gitignoreSource;
70
68
};
71
-
spindleDeps = {
72
-
inherit goModHash gitignoreSource;
73
-
};
74
-
mkPackageSet = pkgs: {
75
-
lexgen = pkgs.callPackage ./nix/pkgs/lexgen.nix {inherit indigo;};
76
-
appview = pkgs.callPackage ./nix/pkgs/appview.nix appviewDeps;
77
-
knot = pkgs.callPackage ./nix/pkgs/knot.nix {};
78
-
spindle = pkgs.callPackage ./nix/pkgs/spindle.nix spindleDeps;
79
-
knot-unwrapped = pkgs.callPackage ./nix/pkgs/knot-unwrapped.nix knotDeps;
80
-
sqlite-lib = pkgs.callPackage ./nix/pkgs/sqlite-lib.nix {
81
-
inherit (pkgs) gcc;
82
-
inherit sqlite-lib-src;
83
-
};
84
-
genjwks = pkgs.callPackage ./nix/pkgs/genjwks.nix {inherit goModHash gitignoreSource;};
85
-
};
86
-
in
87
-
mkPackageSet final;
69
+
spindle = pkgs.callPackage ./nix/pkgs/spindle.nix {inherit sqlite-lib goModHash gitignoreSource;};
70
+
knot-unwrapped = pkgs.callPackage ./nix/pkgs/knot-unwrapped.nix {inherit sqlite-lib goModHash gitignoreSource;};
71
+
knot = pkgs.callPackage ./nix/pkgs/knot.nix {inherit knot-unwrapped;};
72
+
in {
73
+
inherit lexgen appview spindle knot-unwrapped knot sqlite-lib genjwks;
74
+
};
75
+
in {
76
+
overlays.default = final: prev: mkPackageSet final;
88
77
89
78
packages = forAllSystems (system: let
90
79
pkgs = nixpkgsFor.${system};
91
-
staticPkgs = pkgs.pkgsStatic;
92
-
crossPkgs = pkgs.pkgsCross.gnu64.pkgsStatic;
80
+
packages = mkPackageSet pkgs;
81
+
staticPackages = mkPackageSet pkgs.pkgsStatic;
82
+
crossPackages = mkPackageSet pkgs.pkgsCross.gnu64.pkgsStatic;
93
83
in {
94
-
appview = pkgs.appview;
95
-
lexgen = pkgs.lexgen;
96
-
knot = pkgs.knot;
97
-
knot-unwrapped = pkgs.knot-unwrapped;
98
-
spindle = pkgs.spindle;
99
-
genjwks = pkgs.genjwks;
100
-
sqlite-lib = pkgs.sqlite-lib;
84
+
appview = packages.appview;
85
+
lexgen = packages.lexgen;
86
+
knot = packages.knot;
87
+
knot-unwrapped = packages.knot-unwrapped;
88
+
spindle = packages.spindle;
89
+
genjwks = packages.genjwks;
90
+
sqlite-lib = packages.sqlite-lib;
101
91
102
-
pkgsStatic-appview = staticPkgs.appview;
103
-
pkgsStatic-knot = staticPkgs.knot;
104
-
pkgsStatic-knot-unwrapped = staticPkgs.knot-unwrapped;
105
-
pkgsStatic-spindle = staticPkgs.spindle;
106
-
pkgsStatic-sqlite-lib = staticPkgs.sqlite-lib;
92
+
pkgsStatic-appview = staticPackages.appview;
93
+
pkgsStatic-knot = staticPackages.knot;
94
+
pkgsStatic-knot-unwrapped = staticPackages.knot-unwrapped;
95
+
pkgsStatic-spindle = staticPackages.spindle;
96
+
pkgsStatic-sqlite-lib = staticPackages.sqlite-lib;
107
97
108
-
pkgsCross-gnu64-pkgsStatic-appview = crossPkgs.appview;
109
-
pkgsCross-gnu64-pkgsStatic-knot = crossPkgs.knot;
110
-
pkgsCross-gnu64-pkgsStatic-knot-unwrapped = crossPkgs.knot-unwrapped;
111
-
pkgsCross-gnu64-pkgsStatic-spindle = crossPkgs.spindle;
98
+
pkgsCross-gnu64-pkgsStatic-appview = crossPackages.appview;
99
+
pkgsCross-gnu64-pkgsStatic-knot = crossPackages.knot;
100
+
pkgsCross-gnu64-pkgsStatic-knot-unwrapped = crossPackages.knot-unwrapped;
101
+
pkgsCross-gnu64-pkgsStatic-spindle = crossPackages.spindle;
112
102
});
113
-
defaultPackage = forAllSystems (system: nixpkgsFor.${system}.appview);
114
-
formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra);
103
+
defaultPackage = forAllSystems (system: self.packages.${system}.appview);
104
+
formatter = forAllSystems (system: nixpkgsFor.${system}.alejandra);
115
105
devShells = forAllSystems (system: let
116
106
pkgs = nixpkgsFor.${system};
107
+
packages' = self.packages.${system};
117
108
staticShell = pkgs.mkShell.override {
118
109
stdenv = pkgs.pkgsStatic.stdenv;
119
110
};
···
124
115
pkgs.air
125
116
pkgs.gopls
126
117
pkgs.httpie
127
-
pkgs.lexgen
128
118
pkgs.litecli
129
119
pkgs.websocat
130
120
pkgs.tailwindcss
131
121
pkgs.nixos-shell
132
122
pkgs.redis
123
+
packages'.lexgen
133
124
];
134
125
shellHook = ''
135
126
mkdir -p appview/pages/static/{fonts,icons}
···
139
130
cp -f ${inter-fonts-src}/web/InterVariable*.woff2 appview/pages/static/fonts/
140
131
cp -f ${inter-fonts-src}/web/InterDisplay*.woff2 appview/pages/static/fonts/
141
132
cp -f ${ibm-plex-mono-src}/fonts/complete/woff2/IBMPlexMono-Regular.woff2 appview/pages/static/fonts/
142
-
export TANGLED_OAUTH_JWKS="$(${pkgs.genjwks}/bin/genjwks)"
133
+
export TANGLED_OAUTH_JWKS="$(${packages'.genjwks}/bin/genjwks)"
143
134
'';
144
135
env.CGO_ENABLED = 1;
145
136
};
···
172
163
watch-tailwind = {
173
164
type = "app";
174
165
program = ''${tailwind-watcher}/bin/run'';
166
+
};
167
+
vm = {
168
+
type = "app";
169
+
program = toString (pkgs.writeShellScript "vm" ''
170
+
${pkgs.nixos-shell}/bin/nixos-shell --flake .#vm
171
+
'');
175
172
};
176
173
});
177
174
+22
-19
input.css
+22
-19
input.css
···
74
74
75
75
@layer components {
76
76
.btn {
77
-
@apply relative z-10 inline-flex min-h-[30px] cursor-pointer items-center
78
-
justify-center bg-transparent px-2 pb-[0.2rem] text-base
79
-
text-gray-900 before:absolute before:inset-0 before:-z-10
80
-
before:block before:rounded before:border before:border-gray-200
81
-
before:bg-white before:drop-shadow-sm
82
-
before:content-[''] hover:before:border-gray-300
83
-
hover:before:bg-gray-50
84
-
hover:before:shadow-[0_2px_2px_0_rgba(20,20,96,0.1),inset_0_-2px_0_0_#f5f5f5]
85
-
focus:outline-none focus-visible:before:outline
86
-
focus-visible:before:outline-4 focus-visible:before:outline-gray-500
87
-
active:before:shadow-[inset_0_2px_2px_0_rgba(20,20,96,0.1)]
88
-
disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:before:border-gray-200
89
-
disabled:hover:before:bg-white disabled:hover:before:shadow-none
90
-
dark:text-gray-100 dark:before:bg-gray-800 dark:before:border-gray-700
91
-
dark:hover:before:border-gray-600 dark:hover:before:bg-gray-700
92
-
dark:hover:before:shadow-[0_2px_2px_0_rgba(0,0,0,0.2),inset_0_-2px_0_0_#2d3748]
93
-
dark:focus-visible:before:outline-gray-400
94
-
dark:active:before:shadow-[inset_0_2px_2px_0_rgba(0,0,0,0.3)]
95
-
dark:disabled:hover:before:bg-gray-800 dark:disabled:hover:before:border-gray-700;
77
+
@apply relative z-10 inline-flex min-h-[30px] cursor-pointer items-center justify-center
78
+
bg-transparent px-2 pb-[0.2rem] text-sm text-gray-900
79
+
before:absolute before:inset-0 before:-z-10 before:block before:rounded
80
+
before:border before:border-gray-200 before:bg-white
81
+
before:shadow-[inset_0_-2px_0_0_rgba(0,0,0,0.1),0_1px_0_0_rgba(0,0,0,0.04)]
82
+
before:content-[''] before:transition-all before:duration-150 before:ease-in-out
83
+
hover:before:shadow-[inset_0_-2px_0_0_rgba(0,0,0,0.15),0_2px_1px_0_rgba(0,0,0,0.06)]
84
+
hover:before:bg-gray-50
85
+
dark:hover:before:bg-gray-700
86
+
active:before:shadow-[inset_0_2px_2px_0_rgba(0,0,0,0.1)]
87
+
focus:outline-none focus-visible:before:outline focus-visible:before:outline-2 focus-visible:before:outline-gray-400
88
+
disabled:cursor-not-allowed disabled:opacity-50
89
+
dark:text-gray-100 dark:before:bg-gray-800 dark:before:border-gray-700;
90
+
}
91
+
92
+
.btn-create {
93
+
@apply btn text-white
94
+
before:bg-green-600 hover:before:bg-green-700
95
+
dark:before:bg-green-700 dark:hover:before:bg-green-800
96
+
before:border before:border-green-700 hover:before:border-green-800
97
+
focus-visible:before:outline-green-500
98
+
disabled:before:bg-green-400 dark:disabled:before:bg-green-600;
96
99
}
97
100
98
101
.prose img {
+305
knotserver/ingester.go
+305
knotserver/ingester.go
···
1
+
package knotserver
2
+
3
+
import (
4
+
"context"
5
+
"encoding/json"
6
+
"fmt"
7
+
"io"
8
+
"net/http"
9
+
"net/url"
10
+
"path/filepath"
11
+
"slices"
12
+
"strings"
13
+
14
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
15
+
"github.com/bluesky-social/indigo/atproto/syntax"
16
+
"github.com/bluesky-social/indigo/xrpc"
17
+
"github.com/bluesky-social/jetstream/pkg/models"
18
+
securejoin "github.com/cyphar/filepath-securejoin"
19
+
"tangled.sh/tangled.sh/core/api/tangled"
20
+
"tangled.sh/tangled.sh/core/appview/idresolver"
21
+
"tangled.sh/tangled.sh/core/knotserver/db"
22
+
"tangled.sh/tangled.sh/core/knotserver/git"
23
+
"tangled.sh/tangled.sh/core/log"
24
+
"tangled.sh/tangled.sh/core/workflow"
25
+
)
26
+
27
+
func (h *Handle) processPublicKey(ctx context.Context, did string, record tangled.PublicKey) error {
28
+
l := log.FromContext(ctx)
29
+
pk := db.PublicKey{
30
+
Did: did,
31
+
PublicKey: record,
32
+
}
33
+
if err := h.db.AddPublicKey(pk); err != nil {
34
+
l.Error("failed to add public key", "error", err)
35
+
return fmt.Errorf("failed to add public key: %w", err)
36
+
}
37
+
l.Info("added public key from firehose", "did", did)
38
+
return nil
39
+
}
40
+
41
+
func (h *Handle) processKnotMember(ctx context.Context, did string, record tangled.KnotMember) error {
42
+
l := log.FromContext(ctx)
43
+
44
+
if record.Domain != h.c.Server.Hostname {
45
+
l.Error("domain mismatch", "domain", record.Domain, "expected", h.c.Server.Hostname)
46
+
return fmt.Errorf("domain mismatch: %s != %s", record.Domain, h.c.Server.Hostname)
47
+
}
48
+
49
+
ok, err := h.e.E.Enforce(did, ThisServer, ThisServer, "server:invite")
50
+
if err != nil || !ok {
51
+
l.Error("failed to add member", "did", did)
52
+
return fmt.Errorf("failed to enforce permissions: %w", err)
53
+
}
54
+
55
+
if err := h.e.AddKnotMember(ThisServer, record.Subject); err != nil {
56
+
l.Error("failed to add member", "error", err)
57
+
return fmt.Errorf("failed to add member: %w", err)
58
+
}
59
+
l.Info("added member from firehose", "member", record.Subject)
60
+
61
+
if err := h.db.AddDid(did); err != nil {
62
+
l.Error("failed to add did", "error", err)
63
+
return fmt.Errorf("failed to add did: %w", err)
64
+
}
65
+
h.jc.AddDid(did)
66
+
67
+
if err := h.fetchAndAddKeys(ctx, did); err != nil {
68
+
return fmt.Errorf("failed to fetch and add keys: %w", err)
69
+
}
70
+
71
+
return nil
72
+
}
73
+
74
+
func (h *Handle) processPull(ctx context.Context, did string, record tangled.RepoPull) error {
75
+
l := log.FromContext(ctx)
76
+
l = l.With("handler", "processPull")
77
+
l = l.With("did", did)
78
+
l = l.With("target_repo", record.TargetRepo)
79
+
l = l.With("target_branch", record.TargetBranch)
80
+
81
+
if record.Source == nil {
82
+
reason := "not a branch-based pull request"
83
+
l.Info("ignoring pull record", "reason", reason)
84
+
return fmt.Errorf("ignoring pull record: %s", reason)
85
+
}
86
+
87
+
if record.Source.Repo != nil {
88
+
reason := "fork based pull"
89
+
l.Info("ignoring pull record", "reason", reason)
90
+
return fmt.Errorf("ignoring pull record: %s", reason)
91
+
}
92
+
93
+
allDids, err := h.db.GetAllDids()
94
+
if err != nil {
95
+
return err
96
+
}
97
+
98
+
// presently: we only process PRs from collaborators for pipelines
99
+
if !slices.Contains(allDids, did) {
100
+
reason := "not a known did"
101
+
l.Info("rejecting pull record", "reason", reason)
102
+
return fmt.Errorf("rejected pull record: %s, %s", reason, did)
103
+
}
104
+
105
+
repoAt, err := syntax.ParseATURI(record.TargetRepo)
106
+
if err != nil {
107
+
return err
108
+
}
109
+
110
+
// resolve this aturi to extract the repo record
111
+
resolver := idresolver.DefaultResolver()
112
+
ident, err := resolver.ResolveIdent(ctx, repoAt.Authority().String())
113
+
if err != nil || ident.Handle.IsInvalidHandle() {
114
+
return fmt.Errorf("failed to resolve handle: %w", err)
115
+
}
116
+
117
+
xrpcc := xrpc.Client{
118
+
Host: ident.PDSEndpoint(),
119
+
}
120
+
121
+
resp, err := comatproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String())
122
+
if err != nil {
123
+
return err
124
+
}
125
+
126
+
repo := resp.Value.Val.(*tangled.Repo)
127
+
128
+
if repo.Knot != h.c.Server.Hostname {
129
+
reason := "not this knot"
130
+
l.Info("rejecting pull record", "reason", reason)
131
+
return fmt.Errorf("rejected pull record: %s", reason)
132
+
}
133
+
134
+
didSlashRepo, err := securejoin.SecureJoin(repo.Owner, repo.Name)
135
+
if err != nil {
136
+
return err
137
+
}
138
+
139
+
repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, didSlashRepo)
140
+
if err != nil {
141
+
return err
142
+
}
143
+
144
+
gr, err := git.Open(repoPath, record.Source.Branch)
145
+
if err != nil {
146
+
return err
147
+
}
148
+
149
+
workflowDir, err := gr.FileTree(ctx, workflow.WorkflowDir)
150
+
if err != nil {
151
+
return err
152
+
}
153
+
154
+
var pipeline workflow.Pipeline
155
+
for _, e := range workflowDir {
156
+
if !e.IsFile {
157
+
continue
158
+
}
159
+
160
+
fpath := filepath.Join(workflow.WorkflowDir, e.Name)
161
+
contents, err := gr.RawContent(fpath)
162
+
if err != nil {
163
+
continue
164
+
}
165
+
166
+
wf, err := workflow.FromFile(e.Name, contents)
167
+
if err != nil {
168
+
// TODO: log here, respond to client that is pushing
169
+
h.l.Error("failed to parse workflow", "err", err, "path", fpath)
170
+
continue
171
+
}
172
+
173
+
pipeline = append(pipeline, wf)
174
+
}
175
+
176
+
trigger := tangled.Pipeline_PullRequestTriggerData{
177
+
Action: "create",
178
+
SourceBranch: record.Source.Branch,
179
+
SourceSha: record.Source.Sha,
180
+
TargetBranch: record.TargetBranch,
181
+
}
182
+
183
+
compiler := workflow.Compiler{
184
+
Trigger: tangled.Pipeline_TriggerMetadata{
185
+
Kind: string(workflow.TriggerKindPullRequest),
186
+
PullRequest: &trigger,
187
+
Repo: &tangled.Pipeline_TriggerRepo{
188
+
Did: repo.Owner,
189
+
Knot: repo.Knot,
190
+
Repo: repo.Name,
191
+
},
192
+
},
193
+
}
194
+
195
+
cp := compiler.Compile(pipeline)
196
+
eventJson, err := json.Marshal(cp)
197
+
if err != nil {
198
+
return err
199
+
}
200
+
201
+
// do not run empty pipelines
202
+
if cp.Workflows == nil {
203
+
return nil
204
+
}
205
+
206
+
event := db.Event{
207
+
Rkey: TID(),
208
+
Nsid: tangled.PipelineNSID,
209
+
EventJson: string(eventJson),
210
+
}
211
+
212
+
return h.db.InsertEvent(event, h.n)
213
+
}
214
+
215
+
func (h *Handle) fetchAndAddKeys(ctx context.Context, did string) error {
216
+
l := log.FromContext(ctx)
217
+
218
+
keysEndpoint, err := url.JoinPath(h.c.AppViewEndpoint, "keys", did)
219
+
if err != nil {
220
+
l.Error("error building endpoint url", "did", did, "error", err.Error())
221
+
return fmt.Errorf("error building endpoint url: %w", err)
222
+
}
223
+
224
+
resp, err := http.Get(keysEndpoint)
225
+
if err != nil {
226
+
l.Error("error getting keys", "did", did, "error", err)
227
+
return fmt.Errorf("error getting keys: %w", err)
228
+
}
229
+
defer resp.Body.Close()
230
+
231
+
if resp.StatusCode == http.StatusNotFound {
232
+
l.Info("no keys found for did", "did", did)
233
+
return nil
234
+
}
235
+
236
+
plaintext, err := io.ReadAll(resp.Body)
237
+
if err != nil {
238
+
l.Error("error reading response body", "error", err)
239
+
return fmt.Errorf("error reading response body: %w", err)
240
+
}
241
+
242
+
for _, key := range strings.Split(string(plaintext), "\n") {
243
+
if key == "" {
244
+
continue
245
+
}
246
+
pk := db.PublicKey{
247
+
Did: did,
248
+
}
249
+
pk.Key = key
250
+
if err := h.db.AddPublicKey(pk); err != nil {
251
+
l.Error("failed to add public key", "error", err)
252
+
return fmt.Errorf("failed to add public key: %w", err)
253
+
}
254
+
}
255
+
return nil
256
+
}
257
+
258
+
func (h *Handle) processMessages(ctx context.Context, event *models.Event) error {
259
+
did := event.Did
260
+
if event.Kind != models.EventKindCommit {
261
+
return nil
262
+
}
263
+
264
+
var err error
265
+
defer func() {
266
+
eventTime := event.TimeUS
267
+
lastTimeUs := eventTime + 1
268
+
fmt.Println("lastTimeUs", lastTimeUs)
269
+
if err := h.db.SaveLastTimeUs(lastTimeUs); err != nil {
270
+
err = fmt.Errorf("(deferred) failed to save last time us: %w", err)
271
+
}
272
+
}()
273
+
274
+
raw := json.RawMessage(event.Commit.Record)
275
+
276
+
switch event.Commit.Collection {
277
+
case tangled.PublicKeyNSID:
278
+
var record tangled.PublicKey
279
+
if err := json.Unmarshal(raw, &record); err != nil {
280
+
return fmt.Errorf("failed to unmarshal record: %w", err)
281
+
}
282
+
if err := h.processPublicKey(ctx, did, record); err != nil {
283
+
return fmt.Errorf("failed to process public key: %w", err)
284
+
}
285
+
286
+
case tangled.KnotMemberNSID:
287
+
var record tangled.KnotMember
288
+
if err := json.Unmarshal(raw, &record); err != nil {
289
+
return fmt.Errorf("failed to unmarshal record: %w", err)
290
+
}
291
+
if err := h.processKnotMember(ctx, did, record); err != nil {
292
+
return fmt.Errorf("failed to process knot member: %w", err)
293
+
}
294
+
case tangled.RepoPullNSID:
295
+
var record tangled.RepoPull
296
+
if err := json.Unmarshal(raw, &record); err != nil {
297
+
return fmt.Errorf("failed to unmarshal record: %w", err)
298
+
}
299
+
if err := h.processPull(ctx, did, record); err != nil {
300
+
return fmt.Errorf("failed to process knot member: %w", err)
301
+
}
302
+
}
303
+
304
+
return err
305
+
}
+2
-6
knotserver/internal.go
+2
-6
knotserver/internal.go
···
147
147
}
148
148
149
149
func (h *InternalHandle) triggerPipeline(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error {
150
-
const (
151
-
WorkflowDir = ".tangled/workflows"
152
-
)
153
-
154
150
didSlashRepo, err := securejoin.SecureJoin(repoDid, repoName)
155
151
if err != nil {
156
152
return err
···
166
162
return err
167
163
}
168
164
169
-
workflowDir, err := gr.FileTree(context.Background(), WorkflowDir)
165
+
workflowDir, err := gr.FileTree(context.Background(), workflow.WorkflowDir)
170
166
if err != nil {
171
167
return err
172
168
}
···
177
173
continue
178
174
}
179
175
180
-
fpath := filepath.Join(WorkflowDir, e.Name)
176
+
fpath := filepath.Join(workflow.WorkflowDir, e.Name)
181
177
contents, err := gr.RawContent(fpath)
182
178
if err != nil {
183
179
continue
-147
knotserver/jetstream.go
-147
knotserver/jetstream.go
···
1
-
package knotserver
2
-
3
-
import (
4
-
"context"
5
-
"encoding/json"
6
-
"fmt"
7
-
"io"
8
-
"net/http"
9
-
"net/url"
10
-
"strings"
11
-
12
-
"github.com/bluesky-social/jetstream/pkg/models"
13
-
"tangled.sh/tangled.sh/core/api/tangled"
14
-
"tangled.sh/tangled.sh/core/knotserver/db"
15
-
"tangled.sh/tangled.sh/core/log"
16
-
)
17
-
18
-
func (h *Handle) processPublicKey(ctx context.Context, did string, record tangled.PublicKey) error {
19
-
l := log.FromContext(ctx)
20
-
pk := db.PublicKey{
21
-
Did: did,
22
-
PublicKey: record,
23
-
}
24
-
if err := h.db.AddPublicKey(pk); err != nil {
25
-
l.Error("failed to add public key", "error", err)
26
-
return fmt.Errorf("failed to add public key: %w", err)
27
-
}
28
-
l.Info("added public key from firehose", "did", did)
29
-
return nil
30
-
}
31
-
32
-
func (h *Handle) processKnotMember(ctx context.Context, did string, record tangled.KnotMember) error {
33
-
l := log.FromContext(ctx)
34
-
35
-
if record.Domain != h.c.Server.Hostname {
36
-
l.Error("domain mismatch", "domain", record.Domain, "expected", h.c.Server.Hostname)
37
-
return fmt.Errorf("domain mismatch: %s != %s", record.Domain, h.c.Server.Hostname)
38
-
}
39
-
40
-
ok, err := h.e.E.Enforce(did, ThisServer, ThisServer, "server:invite")
41
-
if err != nil || !ok {
42
-
l.Error("failed to add member", "did", did)
43
-
return fmt.Errorf("failed to enforce permissions: %w", err)
44
-
}
45
-
46
-
if err := h.e.AddKnotMember(ThisServer, record.Subject); err != nil {
47
-
l.Error("failed to add member", "error", err)
48
-
return fmt.Errorf("failed to add member: %w", err)
49
-
}
50
-
l.Info("added member from firehose", "member", record.Subject)
51
-
52
-
if err := h.db.AddDid(did); err != nil {
53
-
l.Error("failed to add did", "error", err)
54
-
return fmt.Errorf("failed to add did: %w", err)
55
-
}
56
-
h.jc.AddDid(did)
57
-
58
-
if err := h.fetchAndAddKeys(ctx, did); err != nil {
59
-
return fmt.Errorf("failed to fetch and add keys: %w", err)
60
-
}
61
-
62
-
return nil
63
-
}
64
-
65
-
func (h *Handle) fetchAndAddKeys(ctx context.Context, did string) error {
66
-
l := log.FromContext(ctx)
67
-
68
-
keysEndpoint, err := url.JoinPath(h.c.AppViewEndpoint, "keys", did)
69
-
if err != nil {
70
-
l.Error("error building endpoint url", "did", did, "error", err.Error())
71
-
return fmt.Errorf("error building endpoint url: %w", err)
72
-
}
73
-
74
-
resp, err := http.Get(keysEndpoint)
75
-
if err != nil {
76
-
l.Error("error getting keys", "did", did, "error", err)
77
-
return fmt.Errorf("error getting keys: %w", err)
78
-
}
79
-
defer resp.Body.Close()
80
-
81
-
if resp.StatusCode == http.StatusNotFound {
82
-
l.Info("no keys found for did", "did", did)
83
-
return nil
84
-
}
85
-
86
-
plaintext, err := io.ReadAll(resp.Body)
87
-
if err != nil {
88
-
l.Error("error reading response body", "error", err)
89
-
return fmt.Errorf("error reading response body: %w", err)
90
-
}
91
-
92
-
for _, key := range strings.Split(string(plaintext), "\n") {
93
-
if key == "" {
94
-
continue
95
-
}
96
-
pk := db.PublicKey{
97
-
Did: did,
98
-
}
99
-
pk.Key = key
100
-
if err := h.db.AddPublicKey(pk); err != nil {
101
-
l.Error("failed to add public key", "error", err)
102
-
return fmt.Errorf("failed to add public key: %w", err)
103
-
}
104
-
}
105
-
return nil
106
-
}
107
-
108
-
func (h *Handle) processMessages(ctx context.Context, event *models.Event) error {
109
-
did := event.Did
110
-
if event.Kind != models.EventKindCommit {
111
-
return nil
112
-
}
113
-
114
-
var err error
115
-
defer func() {
116
-
eventTime := event.TimeUS
117
-
lastTimeUs := eventTime + 1
118
-
fmt.Println("lastTimeUs", lastTimeUs)
119
-
if err := h.db.SaveLastTimeUs(lastTimeUs); err != nil {
120
-
err = fmt.Errorf("(deferred) failed to save last time us: %w", err)
121
-
}
122
-
}()
123
-
124
-
raw := json.RawMessage(event.Commit.Record)
125
-
126
-
switch event.Commit.Collection {
127
-
case tangled.PublicKeyNSID:
128
-
var record tangled.PublicKey
129
-
if err := json.Unmarshal(raw, &record); err != nil {
130
-
return fmt.Errorf("failed to unmarshal record: %w", err)
131
-
}
132
-
if err := h.processPublicKey(ctx, did, record); err != nil {
133
-
return fmt.Errorf("failed to process public key: %w", err)
134
-
}
135
-
136
-
case tangled.KnotMemberNSID:
137
-
var record tangled.KnotMember
138
-
if err := json.Unmarshal(raw, &record); err != nil {
139
-
return fmt.Errorf("failed to unmarshal record: %w", err)
140
-
}
141
-
if err := h.processKnotMember(ctx, did, record); err != nil {
142
-
return fmt.Errorf("failed to process knot member: %w", err)
143
-
}
144
-
}
145
-
146
-
return err
147
-
}
+1
knotserver/server.go
+1
knotserver/server.go
···
75
75
jc, err := jetstream.NewJetstreamClient(c.Server.JetstreamEndpoint, "knotserver", []string{
76
76
tangled.PublicKeyNSID,
77
77
tangled.KnotMemberNSID,
78
+
tangled.RepoPullNSID,
78
79
}, nil, logger, db, true, c.Server.LogDids)
79
80
if err != nil {
80
81
logger.Error("failed to setup jetstream", "error", err)
+34
lexicons/feed/reaction.json
+34
lexicons/feed/reaction.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.tangled.feed.reaction",
4
+
"needsCbor": true,
5
+
"needsType": true,
6
+
"defs": {
7
+
"main": {
8
+
"type": "record",
9
+
"key": "tid",
10
+
"record": {
11
+
"type": "object",
12
+
"required": [
13
+
"subject",
14
+
"reaction",
15
+
"createdAt"
16
+
],
17
+
"properties": {
18
+
"subject": {
19
+
"type": "string",
20
+
"format": "at-uri"
21
+
},
22
+
"reaction": {
23
+
"type": "string",
24
+
"enum": [ "๐", "๐", "๐", "๐", "๐ซค", "โค๏ธ", "๐", "๐" ]
25
+
},
26
+
"createdAt": {
27
+
"type": "string",
28
+
"format": "datetime"
29
+
}
30
+
}
31
+
}
32
+
}
33
+
}
34
+
}
+85
-54
lexicons/pipeline.json
+85
-54
lexicons/pipeline.json
···
9
9
"key": "tid",
10
10
"record": {
11
11
"type": "object",
12
-
"required": ["triggerMetadata", "workflows"],
12
+
"required": [
13
+
"triggerMetadata",
14
+
"workflows"
15
+
],
13
16
"properties": {
14
17
"triggerMetadata": {
15
18
"type": "ref",
···
27
30
},
28
31
"triggerMetadata": {
29
32
"type": "object",
30
-
"required": ["kind", "repo"],
33
+
"required": [
34
+
"kind",
35
+
"repo"
36
+
],
31
37
"properties": {
32
38
"kind": {
33
39
"type": "string",
34
-
"enum": ["push", "pull_request", "manual"]
40
+
"enum": [
41
+
"push",
42
+
"pull_request",
43
+
"manual"
44
+
]
35
45
},
36
46
"repo": {
37
47
"type": "ref",
···
53
63
},
54
64
"triggerRepo": {
55
65
"type": "object",
56
-
"required": ["knot", "did", "repo", "defaultBranch"],
66
+
"required": [
67
+
"knot",
68
+
"did",
69
+
"repo",
70
+
"defaultBranch"
71
+
],
57
72
"properties": {
58
73
"knot": {
59
74
"type": "string"
···
72
87
},
73
88
"pushTriggerData": {
74
89
"type": "object",
75
-
"required": ["ref", "newSha", "oldSha"],
90
+
"required": [
91
+
"ref",
92
+
"newSha",
93
+
"oldSha"
94
+
],
76
95
"properties": {
77
96
"ref": {
78
97
"type": "string"
···
91
110
},
92
111
"pullRequestTriggerData": {
93
112
"type": "object",
94
-
"required": ["sourceBranch", "targetBranch", "sourceSha", "action"],
113
+
"required": [
114
+
"sourceBranch",
115
+
"targetBranch",
116
+
"sourceSha",
117
+
"action"
118
+
],
95
119
"properties": {
96
120
"sourceBranch": {
97
121
"type": "string"
···
115
139
"inputs": {
116
140
"type": "array",
117
141
"items": {
118
-
"type": "object",
119
-
"required": ["key", "value"],
120
-
"properties": {
121
-
"key": {
122
-
"type": "string"
123
-
},
124
-
"value": {
125
-
"type": "string"
126
-
}
127
-
}
142
+
"type": "ref",
143
+
"ref": "#pair"
128
144
}
129
145
}
130
146
}
131
147
},
132
148
"workflow": {
133
149
"type": "object",
134
-
"required": ["name", "dependencies", "steps", "environment", "clone"],
150
+
"required": [
151
+
"name",
152
+
"dependencies",
153
+
"steps",
154
+
"environment",
155
+
"clone"
156
+
],
135
157
"properties": {
136
158
"name": {
137
159
"type": "string"
138
160
},
139
161
"dependencies": {
140
-
"type": "ref",
141
-
"ref": "#dependencies"
162
+
"type": "array",
163
+
"items": {
164
+
"type": "ref",
165
+
"ref": "#dependency"
166
+
}
142
167
},
143
168
"steps": {
144
169
"type": "array",
···
150
175
"environment": {
151
176
"type": "array",
152
177
"items": {
153
-
"type": "object",
154
-
"required": ["key", "value"],
155
-
"properties": {
156
-
"key": {
157
-
"type": "string"
158
-
},
159
-
"value": {
160
-
"type": "string"
161
-
}
162
-
}
178
+
"type": "ref",
179
+
"ref": "#pair"
163
180
}
164
181
},
165
182
"clone": {
···
168
185
}
169
186
}
170
187
},
171
-
"dependencies": {
172
-
"type": "array",
173
-
"items": {
174
-
"type": "object",
175
-
"required": ["registry", "packages"],
176
-
"properties": {
177
-
"registry": {
188
+
"dependency": {
189
+
"type": "object",
190
+
"required": [
191
+
"registry",
192
+
"packages"
193
+
],
194
+
"properties": {
195
+
"registry": {
196
+
"type": "string"
197
+
},
198
+
"packages": {
199
+
"type": "array",
200
+
"items": {
178
201
"type": "string"
179
-
},
180
-
"packages": {
181
-
"type": "array",
182
-
"items": {
183
-
"type": "string"
184
-
}
185
202
}
186
203
}
187
204
}
188
205
},
189
206
"cloneOpts": {
190
207
"type": "object",
191
-
"required": ["skip", "depth", "submodules"],
208
+
"required": [
209
+
"skip",
210
+
"depth",
211
+
"submodules"
212
+
],
192
213
"properties": {
193
214
"skip": {
194
215
"type": "boolean"
···
203
224
},
204
225
"step": {
205
226
"type": "object",
206
-
"required": ["name", "command"],
227
+
"required": [
228
+
"name",
229
+
"command"
230
+
],
207
231
"properties": {
208
232
"name": {
209
233
"type": "string"
···
214
238
"environment": {
215
239
"type": "array",
216
240
"items": {
217
-
"type": "object",
218
-
"required": ["key", "value"],
219
-
"properties": {
220
-
"key": {
221
-
"type": "string"
222
-
},
223
-
"value": {
224
-
"type": "string"
225
-
}
226
-
}
241
+
"type": "ref",
242
+
"ref": "#pair"
227
243
}
244
+
}
245
+
}
246
+
},
247
+
"pair": {
248
+
"type": "object",
249
+
"required": [
250
+
"key",
251
+
"value"
252
+
],
253
+
"properties": {
254
+
"key": {
255
+
"type": "string"
256
+
},
257
+
"value": {
258
+
"type": "string"
228
259
}
229
260
}
230
261
}
+7
-1
lexicons/pulls/pull.json
+7
-1
lexicons/pulls/pull.json
···
51
51
"source": {
52
52
"type": "object",
53
53
"required": [
54
-
"branch"
54
+
"branch",
55
+
"sha"
55
56
],
56
57
"properties": {
57
58
"branch": {
58
59
"type": "string"
60
+
},
61
+
"sha": {
62
+
"type": "string",
63
+
"minLength": 40,
64
+
"maxLength": 40
59
65
},
60
66
"repo": {
61
67
"type": "string",
+1
-1
spindle/engine/engine.go
+1
-1
spindle/engine/engine.go
···
90
90
91
91
reader, err := e.docker.ImagePull(ctx, w.Image, image.PullOptions{})
92
92
if err != nil {
93
-
e.l.Error("pipeline failed!", "workflowId", wid, "error", err.Error())
93
+
e.l.Error("pipeline image pull failed!", "image", w.Image, "workflowId", wid, "error", err.Error())
94
94
95
95
err := e.db.StatusFailed(wid, err.Error(), -1, e.n)
96
96
if err != nil {
+2
-2
spindle/engine/envs_test.go
+2
-2
spindle/engine/envs_test.go
···
34
34
if got == nil {
35
35
got = EnvVars{}
36
36
}
37
-
assert.Equal(t, tt.want, got)
37
+
assert.ElementsMatch(t, tt.want, got)
38
38
})
39
39
}
40
40
}
···
44
44
ev.AddEnv("FOO", "bar")
45
45
ev.AddEnv("BAZ", "qux")
46
46
want := EnvVars{"FOO=bar", "BAZ=qux"}
47
-
assert.Equal(t, want, ev)
47
+
assert.ElementsMatch(t, want, ev)
48
48
}
+9
-6
spindle/models/pipeline.go
+9
-6
spindle/models/pipeline.go
···
69
69
70
70
setup.addStep(nixConfStep())
71
71
setup.addStep(cloneStep(*twf, *pl.TriggerMetadata, cfg.Server.Dev))
72
-
setup.addStep(checkoutStep(*twf, *pl.TriggerMetadata))
73
72
// this step could be empty
74
73
if s := dependencyStep(*twf); s != nil {
75
74
setup.addStep(*s)
···
83
82
return &Pipeline{Workflows: workflows}
84
83
}
85
84
86
-
func workflowEnvToMap(envs []*tangled.Pipeline_Workflow_Environment_Elem) map[string]string {
85
+
func workflowEnvToMap(envs []*tangled.Pipeline_Pair) map[string]string {
87
86
envMap := map[string]string{}
88
87
for _, env := range envs {
89
-
envMap[env.Key] = env.Value
88
+
if env != nil {
89
+
envMap[env.Key] = env.Value
90
+
}
90
91
}
91
92
return envMap
92
93
}
93
94
94
-
func stepEnvToMap(envs []*tangled.Pipeline_Step_Environment_Elem) map[string]string {
95
+
func stepEnvToMap(envs []*tangled.Pipeline_Pair) map[string]string {
95
96
envMap := map[string]string{}
96
97
for _, env := range envs {
97
-
envMap[env.Key] = env.Value
98
+
if env != nil {
99
+
envMap[env.Key] = env.Value
100
+
}
98
101
}
99
102
return envMap
100
103
}
101
104
102
-
func workflowImage(deps []tangled.Pipeline_Dependencies_Elem, nixery string) string {
105
+
func workflowImage(deps []*tangled.Pipeline_Dependency, nixery string) string {
103
106
var dependencies string
104
107
for _, d := range deps {
105
108
if d.Registry == "nixpkgs" {
+45
-56
spindle/models/setup_steps.go
+45
-56
spindle/models/setup_steps.go
···
5
5
"path"
6
6
"strings"
7
7
8
-
"github.com/go-git/go-git/v5/plumbing"
9
8
"tangled.sh/tangled.sh/core/api/tangled"
10
9
"tangled.sh/tangled.sh/core/workflow"
11
10
)
···
19
18
}
20
19
}
21
20
22
-
// checkoutStep checks out the specified ref in the cloned repository.
23
-
func checkoutStep(twf tangled.Pipeline_Workflow, tr tangled.Pipeline_TriggerMetadata) Step {
24
-
if twf.Clone.Skip {
25
-
return Step{}
26
-
}
27
-
28
-
var ref string
29
-
switch tr.Kind {
30
-
case "push":
31
-
ref = tr.Push.NewSha
32
-
case "pull_request":
33
-
ref = tr.PullRequest.TargetBranch
34
-
35
-
// TODO: this needs to be specified in lexicon
36
-
case "manual":
37
-
ref = tr.Repo.DefaultBranch
38
-
}
39
-
40
-
checkoutCmd := fmt.Sprintf("git config advice.detachedHead false; git checkout --progress --force %s", ref)
41
-
42
-
return Step{
43
-
Command: checkoutCmd,
44
-
Name: "Checkout ref " + ref,
45
-
}
46
-
}
47
-
48
21
// cloneOptsAsSteps processes clone options and adds corresponding steps
49
22
// to the beginning of the workflow's step list if cloning is not skipped.
23
+
//
24
+
// the steps to do here are:
25
+
// - git init
26
+
// - git remote add origin <url>
27
+
// - git fetch --depth=<d> --recurse-submodules=<yes|no> <sha>
28
+
// - git checkout FETCH_HEAD
50
29
func cloneStep(twf tangled.Pipeline_Workflow, tr tangled.Pipeline_TriggerMetadata, dev bool) Step {
51
30
if twf.Clone.Skip {
52
31
return Step{}
53
32
}
54
33
55
-
uri := "https://"
34
+
var commands []string
35
+
36
+
// initialize git repo in workspace
37
+
commands = append(commands, "git init")
38
+
39
+
// add repo as git remote
40
+
scheme := "https://"
56
41
if dev {
57
-
uri = "http://"
42
+
scheme = "http://"
58
43
tr.Repo.Knot = strings.ReplaceAll(tr.Repo.Knot, "localhost", "host.docker.internal")
59
44
}
45
+
url := scheme + path.Join(tr.Repo.Knot, tr.Repo.Did, tr.Repo.Repo)
46
+
commands = append(commands, fmt.Sprintf("git remote add origin %s", url))
60
47
61
-
cloneUrl := uri + path.Join(tr.Repo.Knot, tr.Repo.Did, tr.Repo.Repo)
62
-
cloneCmd := []string{"git", "clone", cloneUrl, "."}
48
+
// run git fetch
49
+
{
50
+
var fetchArgs []string
51
+
52
+
// default clone depth is 1
53
+
depth := 1
54
+
if twf.Clone.Depth > 1 {
55
+
depth = int(twf.Clone.Depth)
56
+
}
57
+
fetchArgs = append(fetchArgs, fmt.Sprintf("--depth=%d", depth))
63
58
64
-
// default clone depth is 1
65
-
cloneDepth := 1
66
-
if twf.Clone.Depth > 1 {
67
-
cloneDepth = int(twf.Clone.Depth)
68
-
}
69
-
cloneCmd = append(cloneCmd, fmt.Sprintf("--depth=%d", cloneDepth))
59
+
// optionally recurse submodules
60
+
if twf.Clone.Submodules {
61
+
fetchArgs = append(fetchArgs, "--recurse-submodules=yes")
62
+
}
70
63
71
-
// select the clone branch
72
-
cloneBranch := ""
73
-
switch tr.Kind {
74
-
case workflow.TriggerKindManual:
75
-
// TODO: unimplemented
76
-
case workflow.TriggerKindPush:
77
-
ref := tr.Push.Ref
78
-
refName := plumbing.ReferenceName(ref)
79
-
cloneBranch = refName.Short()
80
-
case workflow.TriggerKindPullRequest:
81
-
cloneBranch = tr.PullRequest.SourceBranch
82
-
}
64
+
// set remote to fetch from
65
+
fetchArgs = append(fetchArgs, "origin")
83
66
84
-
if cloneBranch != "" {
85
-
cloneCmd = append(cloneCmd, fmt.Sprintf("--branch=%s", cloneBranch))
86
-
}
67
+
// set revision to checkout
68
+
switch workflow.TriggerKind(tr.Kind) {
69
+
case workflow.TriggerKindManual:
70
+
// TODO: unimplemented
71
+
case workflow.TriggerKindPush:
72
+
fetchArgs = append(fetchArgs, tr.Push.NewSha)
73
+
case workflow.TriggerKindPullRequest:
74
+
fetchArgs = append(fetchArgs, tr.PullRequest.SourceSha)
75
+
}
87
76
88
-
if twf.Clone.Submodules {
89
-
cloneCmd = append(cloneCmd, "--recursive")
77
+
commands = append(commands, fmt.Sprintf("git fetch %s", strings.Join(fetchArgs, " ")))
90
78
}
91
79
92
-
fmt.Println(strings.Join(cloneCmd, " "))
80
+
// run git checkout
81
+
commands = append(commands, "git checkout FETCH_HEAD")
93
82
94
83
cloneStep := Step{
95
-
Command: strings.Join(cloneCmd, " "),
84
+
Command: strings.Join(commands, "\n"),
96
85
Name: "Clone repository into workspace",
97
86
}
98
87
return cloneStep
+2
-2
workflow/compile.go
+2
-2
workflow/compile.go
···
98
98
Name: s.Name,
99
99
}
100
100
for k, v := range s.Environment {
101
-
e := &tangled.Pipeline_Step_Environment_Elem{
101
+
e := &tangled.Pipeline_Pair{
102
102
Key: k,
103
103
Value: v,
104
104
}
···
107
107
cw.Steps = append(cw.Steps, &step)
108
108
}
109
109
for k, v := range w.Environment {
110
-
e := &tangled.Pipeline_Workflow_Environment_Elem{
110
+
e := &tangled.Pipeline_Pair{
111
111
Key: k,
112
112
Value: v,
113
113
}
+1
-1
workflow/compile_test.go
+1
-1
workflow/compile_test.go
+15
-6
workflow/def.go
+15
-6
workflow/def.go
···
4
4
"errors"
5
5
"fmt"
6
6
"slices"
7
+
"strings"
7
8
8
9
"tangled.sh/tangled.sh/core/api/tangled"
9
10
···
51
52
}
52
53
53
54
StringList []string
55
+
56
+
TriggerKind string
54
57
)
55
58
56
59
const (
57
-
TriggerKindPush string = "push"
58
-
TriggerKindPullRequest string = "pull_request"
59
-
TriggerKindManual string = "manual"
60
+
WorkflowDir = ".tangled/workflows"
61
+
62
+
TriggerKindPush TriggerKind = "push"
63
+
TriggerKindPullRequest TriggerKind = "pull_request"
64
+
TriggerKindManual TriggerKind = "manual"
60
65
)
66
+
67
+
func (t TriggerKind) String() string {
68
+
return strings.ReplaceAll(string(t), "_", " ")
69
+
}
61
70
62
71
func FromFile(name string, contents []byte) (Workflow, error) {
63
72
var wf Workflow
···
167
176
}
168
177
169
178
// conversion utilities to atproto records
170
-
func (d Dependencies) AsRecord() []tangled.Pipeline_Dependencies_Elem {
171
-
var deps []tangled.Pipeline_Dependencies_Elem
179
+
func (d Dependencies) AsRecord() []*tangled.Pipeline_Dependency {
180
+
var deps []*tangled.Pipeline_Dependency
172
181
for registry, packages := range d {
173
-
deps = append(deps, tangled.Pipeline_Dependencies_Elem{
182
+
deps = append(deps, &tangled.Pipeline_Dependency{
174
183
Registry: registry,
175
184
Packages: packages,
176
185
})