1package identity
2
3import (
4 "crypto/sha256"
5 "encoding/json"
6 "fmt"
7 "strings"
8
9 "github.com/MichaelMure/git-bug/entity"
10 "github.com/MichaelMure/git-bug/repository"
11 "github.com/MichaelMure/git-bug/util/lamport"
12 "github.com/MichaelMure/git-bug/util/text"
13 "github.com/MichaelMure/git-bug/util/timestamp"
14)
15
16var _ Interface = &Bare{}
17var _ entity.Interface = &Bare{}
18
19// Bare is a very minimal identity, designed to be fully embedded directly along
20// other data.
21//
22// in particular, this identity is designed to be compatible with the handling of
23// identities in the early version of git-bug.
24type Bare struct {
25 id entity.Id
26 name string
27 email string
28 avatarUrl string
29}
30
31func NewBare(name string, email string) *Bare {
32 return &Bare{id: entity.UnsetId, name: name, email: email}
33}
34
35func NewBareFull(name string, email string, avatarUrl string) *Bare {
36 return &Bare{id: entity.UnsetId, name: name, email: email, avatarUrl: avatarUrl}
37}
38
39func deriveId(data []byte) entity.Id {
40 sum := sha256.Sum256(data)
41 return entity.Id(fmt.Sprintf("%x", sum))
42}
43
44type bareIdentityJSON struct {
45 Name string `json:"name,omitempty"`
46 Email string `json:"email,omitempty"`
47 Login string `json:"login,omitempty"` // Deprecated, only kept to have the same ID when reading an old value
48 AvatarUrl string `json:"avatar_url,omitempty"`
49}
50
51func (i *Bare) MarshalJSON() ([]byte, error) {
52 return json.Marshal(bareIdentityJSON{
53 Name: i.name,
54 Email: i.email,
55 AvatarUrl: i.avatarUrl,
56 })
57}
58
59func (i *Bare) UnmarshalJSON(data []byte) error {
60 // Compute the Id when loading the op from disk.
61 i.id = deriveId(data)
62
63 aux := bareIdentityJSON{}
64
65 if err := json.Unmarshal(data, &aux); err != nil {
66 return err
67 }
68
69 i.name = aux.Name
70 i.email = aux.Email
71 i.avatarUrl = aux.AvatarUrl
72
73 return nil
74}
75
76// Id return the Identity identifier
77func (i *Bare) Id() entity.Id {
78 // We don't have a proper Id at hand, so let's hash all the data to get one.
79
80 if i.id == "" {
81 // something went really wrong
82 panic("identity's id not set")
83 }
84 if i.id == entity.UnsetId {
85 // This means we are trying to get the identity identifier *before* it has been stored
86 // As the Id is computed based on the actual bytes written on the disk, we are going to predict
87 // those and then get the Id. This is safe as it will be the exact same code writing on disk later.
88
89 data, err := json.Marshal(i)
90 if err != nil {
91 panic(err)
92 }
93
94 i.id = deriveId(data)
95 }
96 return i.id
97}
98
99// Name return the last version of the name
100func (i *Bare) Name() string {
101 return i.name
102}
103
104// Email return the last version of the email
105func (i *Bare) Email() string {
106 return i.email
107}
108
109// AvatarUrl return the last version of the Avatar URL
110func (i *Bare) AvatarUrl() string {
111 return i.avatarUrl
112}
113
114// Keys return the last version of the valid keys
115func (i *Bare) Keys() []Key {
116 return []Key{}
117}
118
119// ValidKeysAtTime return the set of keys valid at a given lamport time
120func (i *Bare) ValidKeysAtTime(time lamport.Time) []Key {
121 return []Key{}
122}
123
124// DisplayName return a non-empty string to display, representing the
125// identity, based on the non-empty values.
126func (i *Bare) DisplayName() string {
127 return i.name
128}
129
130// Validate check if the Identity data is valid
131func (i *Bare) Validate() error {
132 if text.Empty(i.name) {
133 return fmt.Errorf("name is not set")
134 }
135
136 if strings.Contains(i.name, "\n") {
137 return fmt.Errorf("name should be a single line")
138 }
139
140 if !text.Safe(i.name) {
141 return fmt.Errorf("name is not fully printable")
142 }
143
144 if strings.Contains(i.email, "\n") {
145 return fmt.Errorf("email should be a single line")
146 }
147
148 if !text.Safe(i.email) {
149 return fmt.Errorf("email is not fully printable")
150 }
151
152 if i.avatarUrl != "" && !text.ValidUrl(i.avatarUrl) {
153 return fmt.Errorf("avatarUrl is not a valid URL")
154 }
155
156 return nil
157}
158
159// Write the identity into the Repository. In particular, this ensure that
160// the Id is properly set.
161func (i *Bare) Commit(repo repository.ClockedRepo) error {
162 // Nothing to do, everything is directly embedded
163 return nil
164}
165
166// If needed, write the identity into the Repository. In particular, this
167// ensure that the Id is properly set.
168func (i *Bare) CommitAsNeeded(repo repository.ClockedRepo) error {
169 // Nothing to do, everything is directly embedded
170 return nil
171}
172
173// IsProtected return true if the chain of git commits started to be signed.
174// If that's the case, only signed commit with a valid key for this identity can be added.
175func (i *Bare) IsProtected() bool {
176 return false
177}
178
179// LastModificationLamportTime return the Lamport time at which the last version of the identity became valid.
180func (i *Bare) LastModificationLamport() lamport.Time {
181 return 0
182}
183
184// LastModification return the timestamp at which the last version of the identity became valid.
185func (i *Bare) LastModification() timestamp.Timestamp {
186 return 0
187}