mirror of
https://github.com/anyproto/any-sync.git
synced 2025-06-08 05:57:03 +09:00
project structure proposal
This commit is contained in:
parent
a06924829e
commit
231c571787
48 changed files with 83 additions and 65 deletions
10
README.md
10
README.md
|
@ -1,2 +1,12 @@
|
|||
# go-anytype-infrastructure-experiments
|
||||
This repository will have the code for new infrastructure client and node prototypes
|
||||
|
||||
## Project structure
|
||||
- **app** - DI, loggers, common engine
|
||||
- **bin** - contains compiled binaries (under gitignore)
|
||||
- **cmd** - main files by directories
|
||||
- **config** - config component
|
||||
- **etc** - default/example config files, keys, etc
|
||||
- **service** - services, runtime components (these packages can use code from everywhere)
|
||||
- **pkg** - some static packages that can be able to move to a separate repo, dependencies of these packages limited to this folder (maybe util)
|
||||
- **util** - helpers
|
BIN
bin/anytype-node
BIN
bin/anytype-node
Binary file not shown.
|
@ -34,34 +34,42 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
// create app
|
||||
ctx := context.Background()
|
||||
a := new(app.App)
|
||||
|
||||
// open config file
|
||||
conf, err := config.NewFromFile(*flagConfigFile)
|
||||
if err != nil {
|
||||
log.Fatal("can't open config file", zap.Error(err))
|
||||
}
|
||||
|
||||
// bootstrap components
|
||||
a.Register(conf)
|
||||
|
||||
Bootstrap(a)
|
||||
|
||||
// start app
|
||||
if err := a.Start(ctx); err != nil {
|
||||
log.Error("can't start app", zap.Error(err))
|
||||
}
|
||||
log.Info("app started", zap.String("version", a.Version()))
|
||||
|
||||
// wait exit signal
|
||||
exit := make(chan os.Signal, 1)
|
||||
signal.Notify(exit, os.Interrupt, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
sig := <-exit
|
||||
log.Info("received exit signal, stop app", zap.String("signal", fmt.Sprint(sig)))
|
||||
log.Info("received exit signal, stop app...", zap.String("signal", fmt.Sprint(sig)))
|
||||
|
||||
// close app
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
defer cancel()
|
||||
if err := a.Close(ctx); err != nil {
|
||||
log.Fatal("close error", zap.Error(err))
|
||||
} else {
|
||||
log.Info("goodbye!")
|
||||
}
|
||||
}
|
||||
|
||||
func Bootstrap(a *app.App) {
|
||||
|
||||
//a.Register(mycomponent.New())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package aclchanges
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/pb"
|
||||
)
|
||||
|
||||
type Change interface {
|
|
@ -4,7 +4,7 @@ import (
|
|||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/textileio/go-threads/crypto/symmetric"
|
||||
"hash/fnv"
|
|
@ -2,8 +2,8 @@ package acltree
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/pb"
|
||||
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
)
|
|
@ -1,8 +1,8 @@
|
|||
package acltree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread"
|
||||
"sync"
|
||||
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
|
@ -1,9 +1,9 @@
|
|||
package acltree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/testutils/threadbuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/threadbuilder"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
|
@ -2,7 +2,7 @@ package acltree
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread"
|
||||
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
|
@ -2,8 +2,8 @@ package acltree
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
||||
"github.com/textileio/go-threads/crypto/symmetric"
|
|
@ -1,8 +1,8 @@
|
|||
package acltree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/gogo/protobuf/proto"
|
|
@ -3,8 +3,8 @@ package acltree
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread"
|
||||
"time"
|
||||
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
|
@ -2,7 +2,7 @@ package acltree
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
)
|
|
@ -1,8 +1,8 @@
|
|||
package acltree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
)
|
||||
|
|
@ -2,10 +2,10 @@ package acltree
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/testutils/threadbuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/threadbuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
|
@ -3,7 +3,7 @@ package acltree
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread"
|
||||
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
//"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/lib/logging"
|
|
@ -2,11 +2,11 @@ package plaintextdocument
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/account"
|
||||
aclpb "github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges/pb"
|
||||
acltree2 "github.com/anytypeio/go-anytype-infrastructure-experiments/acl/acltree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/testutils/testchanges/pb"
|
||||
thread2 "github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
aclpb "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/acltree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
@ -18,7 +18,7 @@ type PlainTextDocument interface {
|
|||
|
||||
type plainTextDocument struct {
|
||||
heads []string
|
||||
aclTree acltree2.ACLTree
|
||||
aclTree acltree.ACLTree
|
||||
state *DocumentState
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ func (p *plainTextDocument) Text() string {
|
|||
}
|
||||
|
||||
func (p *plainTextDocument) AddText(text string) error {
|
||||
_, err := p.aclTree.AddContent(func(builder acltree2.ChangeBuilder) error {
|
||||
_, err := p.aclTree.AddContent(func(builder acltree.ChangeBuilder) error {
|
||||
builder.AddChangeContent(
|
||||
&pb.PlainTextChangeData{
|
||||
Content: []*pb.PlainTextChangeContent{
|
||||
|
@ -42,7 +42,7 @@ func (p *plainTextDocument) AddText(text string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (p *plainTextDocument) Update(tree acltree2.ACLTree) {
|
||||
func (p *plainTextDocument) Update(tree acltree.ACLTree) {
|
||||
p.aclTree = tree
|
||||
var err error
|
||||
defer func() {
|
||||
|
@ -54,7 +54,7 @@ func (p *plainTextDocument) Update(tree acltree2.ACLTree) {
|
|||
prevHeads := p.heads
|
||||
p.heads = tree.Heads()
|
||||
startId := prevHeads[0]
|
||||
tree.IterateFrom(startId, func(change *acltree2.Change) (isContinue bool) {
|
||||
tree.IterateFrom(startId, func(change *acltree.Change) (isContinue bool) {
|
||||
if change.Id == startId {
|
||||
return true
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func (p *plainTextDocument) Update(tree acltree2.ACLTree) {
|
|||
})
|
||||
}
|
||||
|
||||
func (p *plainTextDocument) Rebuild(tree acltree2.ACLTree) {
|
||||
func (p *plainTextDocument) Rebuild(tree acltree.ACLTree) {
|
||||
p.aclTree = tree
|
||||
p.heads = tree.Heads()
|
||||
var startId string
|
||||
|
@ -92,7 +92,7 @@ func (p *plainTextDocument) Rebuild(tree acltree2.ACLTree) {
|
|||
}
|
||||
|
||||
startId = rootChange.Id
|
||||
tree.Iterate(func(change *acltree2.Change) (isContinue bool) {
|
||||
tree.Iterate(func(change *acltree.Change) (isContinue bool) {
|
||||
if startId == change.Id {
|
||||
return true
|
||||
}
|
||||
|
@ -111,14 +111,14 @@ func (p *plainTextDocument) Rebuild(tree acltree2.ACLTree) {
|
|||
}
|
||||
|
||||
func NewInMemoryPlainTextDocument(acc *account.AccountData, text string) (PlainTextDocument, error) {
|
||||
return NewPlainTextDocument(acc, thread2.NewInMemoryThread, text)
|
||||
return NewPlainTextDocument(acc, thread.NewInMemoryThread, text)
|
||||
}
|
||||
|
||||
func NewPlainTextDocument(
|
||||
acc *account.AccountData,
|
||||
create func(change *thread2.RawChange) (thread2.Thread, error),
|
||||
create func(change *thread.RawChange) (thread.Thread, error),
|
||||
text string) (PlainTextDocument, error) {
|
||||
changeBuilder := func(builder acltree2.ChangeBuilder) error {
|
||||
changeBuilder := func(builder acltree.ChangeBuilder) error {
|
||||
err := builder.UserAdd(acc.Identity, acc.EncKey.GetPublic(), aclpb.ACLChange_Admin)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -126,7 +126,7 @@ func NewPlainTextDocument(
|
|||
builder.AddChangeContent(createInitialChangeContent(text))
|
||||
return nil
|
||||
}
|
||||
t, err := acltree2.BuildThreadWithACL(
|
||||
t, err := acltree.BuildThreadWithACL(
|
||||
acc,
|
||||
changeBuilder,
|
||||
create)
|
||||
|
@ -139,7 +139,7 @@ func NewPlainTextDocument(
|
|||
aclTree: nil,
|
||||
state: nil,
|
||||
}
|
||||
tree, err := acltree2.BuildACLTree(t, acc, doc)
|
||||
tree, err := acltree.BuildACLTree(t, acc, doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package plaintextdocument
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/testutils/threadbuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/threadbuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
|
@ -2,7 +2,7 @@ package plaintextdocument
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/testutils/testchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/pb"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
|
@ -3,12 +3,12 @@ package threadbuilder
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges/pb"
|
||||
testpb "github.com/anytypeio/go-anytype-infrastructure-experiments/acl/testutils/testchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/testutils/yamltests"
|
||||
thread2 "github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
|
||||
threadpb "github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/pb"
|
||||
testpb "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/yamltests"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread"
|
||||
threadpb "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
|
@ -87,7 +87,7 @@ func (t *ThreadBuilder) Heads() []string {
|
|||
return t.heads
|
||||
}
|
||||
|
||||
func (t *ThreadBuilder) AddRawChange(change *thread2.RawChange) error {
|
||||
func (t *ThreadBuilder) AddRawChange(change *thread.RawChange) error {
|
||||
aclChange := new(pb.ACLChange)
|
||||
var err error
|
||||
|
||||
|
@ -162,12 +162,12 @@ func (t *ThreadBuilder) RemoveOrphans(orphans ...string) {
|
|||
t.orphans = slice.Difference(t.orphans, orphans)
|
||||
}
|
||||
|
||||
func (t *ThreadBuilder) GetChange(ctx context.Context, recordID string) (*thread2.RawChange, error) {
|
||||
func (t *ThreadBuilder) GetChange(ctx context.Context, recordID string) (*thread.RawChange, error) {
|
||||
return t.getChange(recordID, t.allChanges), nil
|
||||
}
|
||||
|
||||
func (t *ThreadBuilder) GetUpdates(useCase string) []*thread2.RawChange {
|
||||
var res []*thread2.RawChange
|
||||
func (t *ThreadBuilder) GetUpdates(useCase string) []*thread.RawChange {
|
||||
var res []*thread.RawChange
|
||||
update := t.updates[useCase]
|
||||
for _, ch := range update.changes {
|
||||
rawCh := t.getChange(ch.id, update.changes)
|
||||
|
@ -180,7 +180,7 @@ func (t *ThreadBuilder) Header() *threadpb.ThreadHeader {
|
|||
return t.header
|
||||
}
|
||||
|
||||
func (t *ThreadBuilder) getChange(changeId string, m map[string]*threadChange) *thread2.RawChange {
|
||||
func (t *ThreadBuilder) getChange(changeId string, m map[string]*threadChange) *thread.RawChange {
|
||||
rec := m[changeId]
|
||||
|
||||
if rec.changesDataDecrypted != nil {
|
||||
|
@ -202,7 +202,7 @@ func (t *ThreadBuilder) getChange(changeId string, m map[string]*threadChange) *
|
|||
panic("should be able to sign final acl message!")
|
||||
}
|
||||
|
||||
transformedRec := &thread2.RawChange{
|
||||
transformedRec := &thread.RawChange{
|
||||
Payload: aclMarshaled,
|
||||
Signature: signature,
|
||||
Id: changeId,
|
||||
|
@ -279,7 +279,7 @@ func (t *ThreadBuilder) parseThreadId(description *ThreadDescription) string {
|
|||
panic("no author in thread")
|
||||
}
|
||||
key := t.keychain.SigningKeys[description.Author]
|
||||
id, err := thread2.CreateACLThreadID(key.GetPublic(), plainTextDocType)
|
||||
id, err := thread.CreateACLThreadID(key.GetPublic(), plainTextDocType)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
|
@ -9,7 +9,7 @@ package threadbuilder
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
testpb "github.com/anytypeio/go-anytype-infrastructure-experiments/acl/testutils/testchanges/pb"
|
||||
testpb "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/pb"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"strings"
|
|
@ -3,8 +3,8 @@ package thread
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
"github.com/gogo/protobuf/proto"
|
|
@ -2,8 +2,8 @@ package thread
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/aclchanges"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread/pb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/thread/pb"
|
||||
)
|
||||
|
||||
// TODO: change methods to have errors as a return parameter, because we will be dealing with a real database
|
Loading…
Add table
Add a link
Reference in a new issue