1// Copyright (C) 2019 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package consoleql
5
6import (
7	"github.com/graphql-go/graphql"
8	"go.uber.org/zap"
9
10	"storj.io/common/uuid"
11	"storj.io/storj/private/post"
12	"storj.io/storj/satellite/console"
13	"storj.io/storj/satellite/mailservice"
14)
15
16const (
17	// Mutation is graphql request that modifies data.
18	Mutation = "mutation"
19
20	// CreateProjectMutation is a mutation name for project creation.
21	CreateProjectMutation = "createProject"
22	// DeleteProjectMutation is a mutation name for project deletion.
23	DeleteProjectMutation = "deleteProject"
24	// UpdateProjectMutation is a mutation name for project name and description updating.
25	UpdateProjectMutation = "updateProject"
26
27	// AddProjectMembersMutation is a mutation name for adding new project members.
28	AddProjectMembersMutation = "addProjectMembers"
29	// DeleteProjectMembersMutation is a mutation name for deleting project members.
30	DeleteProjectMembersMutation = "deleteProjectMembers"
31
32	// CreateAPIKeyMutation is a mutation name for api key creation.
33	CreateAPIKeyMutation = "createAPIKey"
34	// DeleteAPIKeysMutation is a mutation name for api key deleting.
35	DeleteAPIKeysMutation = "deleteAPIKeys"
36
37	// AddPaymentMethodMutation is mutation name for adding new payment method.
38	AddPaymentMethodMutation = "addPaymentMethod"
39	// DeletePaymentMethodMutation is mutation name for deleting payment method.
40	DeletePaymentMethodMutation = "deletePaymentMethod"
41	// SetDefaultPaymentMethodMutation is mutation name setting payment method as default payment method.
42	SetDefaultPaymentMethodMutation = "setDefaultPaymentMethod"
43
44	// InputArg is argument name for all input types.
45	InputArg = "input"
46	// ProjectFields is a field name for project specific fields.
47	ProjectFields = "projectFields"
48	// ProjectLimits is a field name for project specific limits.
49	ProjectLimits = "projectLimits"
50	// FieldProjectID is field name for projectID.
51	FieldProjectID = "projectID"
52	// FieldNewPassword is a field name for new password.
53	FieldNewPassword = "newPassword"
54	// Secret is a field name for registration token for user creation during Vanguard release.
55	Secret = "secret"
56	// ReferrerUserID is a field name for passing referrer's user id.
57	ReferrerUserID = "referrerUserId"
58)
59
60// rootMutation creates mutation for graphql populated by AccountsClient.
61func rootMutation(log *zap.Logger, service *console.Service, mailService *mailservice.Service, types *TypeCreator) *graphql.Object {
62	return graphql.NewObject(graphql.ObjectConfig{
63		Name: Mutation,
64		Fields: graphql.Fields{
65			// creates project from input params
66			CreateProjectMutation: &graphql.Field{
67				Type: types.project,
68				Args: graphql.FieldConfigArgument{
69					InputArg: &graphql.ArgumentConfig{
70						Type: graphql.NewNonNull(types.projectInput),
71					},
72				},
73				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
74					var projectInput = fromMapProjectInfo(p.Args[InputArg].(map[string]interface{}))
75
76					project, err := service.CreateProject(p.Context, projectInput)
77					if err != nil {
78						return nil, err
79					}
80
81					return project, nil
82				},
83			},
84			// deletes project by id, taken from input params
85			DeleteProjectMutation: &graphql.Field{
86				Type: types.project,
87				Args: graphql.FieldConfigArgument{
88					FieldID: &graphql.ArgumentConfig{
89						Type: graphql.NewNonNull(graphql.String),
90					},
91				},
92				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
93					return nil, console.ErrUnauthorized.New("not implemented")
94				},
95			},
96			// updates project name and description.
97			UpdateProjectMutation: &graphql.Field{
98				Type: types.project,
99				Args: graphql.FieldConfigArgument{
100					FieldID: &graphql.ArgumentConfig{
101						Type: graphql.NewNonNull(graphql.String),
102					},
103					ProjectFields: &graphql.ArgumentConfig{
104						Type: graphql.NewNonNull(types.projectInput),
105					},
106					ProjectLimits: &graphql.ArgumentConfig{
107						Type: graphql.NewNonNull(types.projectLimit),
108					},
109				},
110				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
111					var projectInput, err = fromMapProjectInfoProjectLimits(p.Args[ProjectFields].(map[string]interface{}), p.Args[ProjectLimits].(map[string]interface{}))
112					if err != nil {
113						return nil, err
114					}
115
116					inputID := p.Args[FieldID].(string)
117					projectID, err := uuid.FromString(inputID)
118					if err != nil {
119						return nil, err
120					}
121
122					project, err := service.UpdateProject(p.Context, projectID, projectInput)
123					if err != nil {
124						return nil, err
125					}
126
127					return project, nil
128				},
129			},
130			// add user as member of given project
131			AddProjectMembersMutation: &graphql.Field{
132				Type: types.project,
133				Args: graphql.FieldConfigArgument{
134					FieldProjectID: &graphql.ArgumentConfig{
135						Type: graphql.NewNonNull(graphql.String),
136					},
137					FieldEmail: &graphql.ArgumentConfig{
138						Type: graphql.NewNonNull(graphql.NewList(graphql.String)),
139					},
140				},
141				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
142					pID, _ := p.Args[FieldProjectID].(string)
143					emails, _ := p.Args[FieldEmail].([]interface{})
144
145					projectID, err := uuid.FromString(pID)
146					if err != nil {
147						return nil, err
148					}
149
150					var userEmails []string
151					for _, email := range emails {
152						userEmails = append(userEmails, email.(string))
153					}
154
155					project, err := service.GetProject(p.Context, projectID)
156					if err != nil {
157						return nil, err
158					}
159
160					users, err := service.AddProjectMembers(p.Context, projectID, userEmails)
161					if err != nil {
162						return nil, err
163					}
164
165					rootObject := p.Info.RootValue.(map[string]interface{})
166					origin := rootObject["origin"].(string)
167					signIn := origin + rootObject[SignInPath].(string)
168
169					for _, user := range users {
170						userName := user.ShortName
171						if user.ShortName == "" {
172							userName = user.FullName
173						}
174
175						contactInfoURL := rootObject[ContactInfoURL].(string)
176						letUsKnowURL := rootObject[LetUsKnowURL].(string)
177						termsAndConditionsURL := rootObject[TermsAndConditionsURL].(string)
178
179						mailService.SendRenderedAsync(
180							p.Context,
181							[]post.Address{{Address: user.Email, Name: userName}},
182							&ProjectInvitationEmail{
183								Origin:                origin,
184								UserName:              userName,
185								ProjectName:           project.Name,
186								SignInLink:            signIn,
187								LetUsKnowURL:          letUsKnowURL,
188								TermsAndConditionsURL: termsAndConditionsURL,
189								ContactInfoURL:        contactInfoURL,
190							},
191						)
192					}
193
194					return project, nil
195				},
196			},
197			// delete user membership for given project
198			DeleteProjectMembersMutation: &graphql.Field{
199				Type: types.project,
200				Args: graphql.FieldConfigArgument{
201					FieldProjectID: &graphql.ArgumentConfig{
202						Type: graphql.NewNonNull(graphql.String),
203					},
204					FieldEmail: &graphql.ArgumentConfig{
205						Type: graphql.NewNonNull(graphql.NewList(graphql.String)),
206					},
207				},
208				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
209					pID, _ := p.Args[FieldProjectID].(string)
210					emails, _ := p.Args[FieldEmail].([]interface{})
211
212					projectID, err := uuid.FromString(pID)
213					if err != nil {
214						return nil, err
215					}
216
217					var userEmails []string
218					for _, email := range emails {
219						userEmails = append(userEmails, email.(string))
220					}
221
222					project, err := service.GetProject(p.Context, projectID)
223					if err != nil {
224						return nil, err
225					}
226
227					err = service.DeleteProjectMembers(p.Context, project.ID, userEmails)
228					if err != nil {
229						return nil, err
230					}
231
232					return project, nil
233				},
234			},
235			// creates new api key
236			CreateAPIKeyMutation: &graphql.Field{
237				Type: types.createAPIKey,
238				Args: graphql.FieldConfigArgument{
239					FieldProjectID: &graphql.ArgumentConfig{
240						Type: graphql.NewNonNull(graphql.String),
241					},
242					FieldName: &graphql.ArgumentConfig{
243						Type: graphql.NewNonNull(graphql.String),
244					},
245				},
246				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
247					projectIDField, _ := p.Args[FieldProjectID].(string)
248					name, _ := p.Args[FieldName].(string)
249
250					projectID, err := uuid.FromString(projectIDField)
251					if err != nil {
252						return nil, err
253					}
254
255					info, key, err := service.CreateAPIKey(p.Context, projectID, name)
256					if err != nil {
257						return nil, err
258					}
259
260					return createAPIKey{
261						Key:     key.Serialize(),
262						KeyInfo: info,
263					}, nil
264				},
265			},
266			// deletes api key
267			DeleteAPIKeysMutation: &graphql.Field{
268				Type: graphql.NewList(types.apiKeyInfo),
269				Args: graphql.FieldConfigArgument{
270					FieldID: &graphql.ArgumentConfig{
271						Type: graphql.NewNonNull(graphql.NewList(graphql.String)),
272					},
273				},
274				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
275					paramKeysID, _ := p.Args[FieldID].([]interface{})
276
277					var keyIds []uuid.UUID
278					var keys []console.APIKeyInfo
279					for _, id := range paramKeysID {
280						keyID, err := uuid.FromString(id.(string))
281						if err != nil {
282							return nil, err
283						}
284
285						key, err := service.GetAPIKeyInfo(p.Context, keyID)
286						if err != nil {
287							return nil, err
288						}
289
290						keyIds = append(keyIds, keyID)
291						keys = append(keys, *key)
292					}
293
294					err := service.DeleteAPIKeys(p.Context, keyIds)
295					if err != nil {
296						return nil, err
297					}
298
299					return keys, nil
300				},
301			},
302			AddPaymentMethodMutation: &graphql.Field{
303				Type: graphql.Boolean,
304				Args: graphql.FieldConfigArgument{},
305				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
306					return nil, nil
307				},
308			},
309			DeletePaymentMethodMutation: &graphql.Field{
310				Type: graphql.Boolean,
311				Args: graphql.FieldConfigArgument{},
312				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
313					return nil, nil
314				},
315			},
316			SetDefaultPaymentMethodMutation: &graphql.Field{
317				Type: graphql.Boolean,
318				Args: graphql.FieldConfigArgument{},
319				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
320					return nil, nil
321				},
322			},
323		},
324	})
325}
326