1// Copyright (C) 2019 Storj Labs, Inc. 2// See LICENSE for copying information. 3 4package consoleql 5 6import ( 7 "strconv" 8 "time" 9 10 "github.com/graphql-go/graphql" 11 12 "storj.io/common/memory" 13 "storj.io/storj/satellite/accounting" 14 "storj.io/storj/satellite/console" 15) 16 17const ( 18 // ProjectType is a graphql type name for project. 19 ProjectType = "project" 20 // ProjectInputType is a graphql type name for project input. 21 ProjectInputType = "projectInput" 22 // ProjectLimitType is a graphql type name for project limit. 23 ProjectLimitType = "projectLimit" 24 // ProjectUsageType is a graphql type name for project usage. 25 ProjectUsageType = "projectUsage" 26 // ProjectsCursorInputType is a graphql input type name for projects cursor. 27 ProjectsCursorInputType = "projectsCursor" 28 // ProjectsPageType is a graphql type name for projects page. 29 ProjectsPageType = "projectsPage" 30 // BucketUsageCursorInputType is a graphql input 31 // type name for bucket usage cursor. 32 BucketUsageCursorInputType = "bucketUsageCursor" 33 // BucketUsageType is a graphql type name for bucket usage. 34 BucketUsageType = "bucketUsage" 35 // BucketUsagePageType is a field name for bucket usage page. 36 BucketUsagePageType = "bucketUsagePage" 37 // ProjectMembersPageType is a field name for project members page. 38 ProjectMembersPageType = "projectMembersPage" 39 // ProjectMembersCursorInputType is a graphql type name for project members. 40 ProjectMembersCursorInputType = "projectMembersCursor" 41 // APIKeysPageType is a field name for api keys page. 42 APIKeysPageType = "apiKeysPage" 43 // APIKeysCursorInputType is a graphql type name for api keys. 44 APIKeysCursorInputType = "apiKeysCursor" 45 // FieldOwnerID is a field name for "ownerId". 46 FieldOwnerID = "ownerId" 47 // FieldName is a field name for "name". 48 FieldName = "name" 49 // FieldBucketName is a field name for "bucket name". 50 FieldBucketName = "bucketName" 51 // FieldDescription is a field name for description. 52 FieldDescription = "description" 53 // FieldMembers is field name for members. 54 FieldMembers = "members" 55 // FieldAPIKeys is a field name for api keys. 56 FieldAPIKeys = "apiKeys" 57 // FieldUsage is a field name for usage rollup. 58 FieldUsage = "usage" 59 // FieldBucketUsages is a field name for bucket usages. 60 FieldBucketUsages = "bucketUsages" 61 // FieldStorageLimit is a field name for the storage limit. 62 FieldStorageLimit = "storageLimit" 63 // FieldBandwidthLimit is a field name for bandwidth limit. 64 FieldBandwidthLimit = "bandwidthLimit" 65 // FieldStorage is a field name for storage total. 66 FieldStorage = "storage" 67 // FieldEgress is a field name for egress total. 68 FieldEgress = "egress" 69 // FieldSegmentCount is a field name for segments count. 70 FieldSegmentCount = "segmentCount" 71 // FieldObjectCount is a field name for objects count. 72 FieldObjectCount = "objectCount" 73 // FieldPageCount is a field name for total page count. 74 FieldPageCount = "pageCount" 75 // FieldCurrentPage is a field name for current page number. 76 FieldCurrentPage = "currentPage" 77 // FieldTotalCount is a field name for bucket usage count total. 78 FieldTotalCount = "totalCount" 79 // FieldMemberCount is a field name for number of project members. 80 FieldMemberCount = "memberCount" 81 // FieldProjects is a field name for projects. 82 FieldProjects = "projects" 83 // FieldProjectMembers is a field name for project members. 84 FieldProjectMembers = "projectMembers" 85 // CursorArg is an argument name for cursor. 86 CursorArg = "cursor" 87 // PageArg ia an argument name for page number. 88 PageArg = "page" 89 // LimitArg is argument name for limit. 90 LimitArg = "limit" 91 // OffsetArg is argument name for offset. 92 OffsetArg = "offset" 93 // SearchArg is argument name for search. 94 SearchArg = "search" 95 // OrderArg is argument name for order. 96 OrderArg = "order" 97 // OrderDirectionArg is argument name for order direction. 98 OrderDirectionArg = "orderDirection" 99 // SinceArg marks start of the period. 100 SinceArg = "since" 101 // BeforeArg marks end of the period. 102 BeforeArg = "before" 103) 104 105// graphqlProject creates *graphql.Object type representation of satellite.ProjectInfo. 106func graphqlProject(service *console.Service, types *TypeCreator) *graphql.Object { 107 return graphql.NewObject(graphql.ObjectConfig{ 108 Name: ProjectType, 109 Fields: graphql.Fields{ 110 FieldID: &graphql.Field{ 111 Type: graphql.String, 112 }, 113 FieldName: &graphql.Field{ 114 Type: graphql.String, 115 }, 116 FieldOwnerID: &graphql.Field{ 117 Type: graphql.String, 118 }, 119 FieldDescription: &graphql.Field{ 120 Type: graphql.String, 121 }, 122 FieldCreatedAt: &graphql.Field{ 123 Type: graphql.DateTime, 124 }, 125 FieldMemberCount: &graphql.Field{ 126 Type: graphql.Int, 127 }, 128 FieldMembers: &graphql.Field{ 129 Type: types.projectMemberPage, 130 Args: graphql.FieldConfigArgument{ 131 CursorArg: &graphql.ArgumentConfig{ 132 Type: graphql.NewNonNull(types.projectMembersCursor), 133 }, 134 }, 135 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 136 project, _ := p.Source.(*console.Project) 137 138 _, err := console.GetAuth(p.Context) 139 if err != nil { 140 return nil, err 141 } 142 143 cursor := cursorArgsToProjectMembersCursor(p.Args[CursorArg].(map[string]interface{})) 144 page, err := service.GetProjectMembers(p.Context, project.ID, cursor) 145 if err != nil { 146 return nil, err 147 } 148 149 var users []projectMember 150 for _, member := range page.ProjectMembers { 151 user, err := service.GetUser(p.Context, member.MemberID) 152 if err != nil { 153 return nil, err 154 } 155 156 users = append(users, projectMember{ 157 User: user, 158 JoinedAt: member.CreatedAt, 159 }) 160 } 161 162 projectMembersPage := projectMembersPage{ 163 ProjectMembers: users, 164 TotalCount: page.TotalCount, 165 Offset: page.Offset, 166 Limit: page.Limit, 167 Order: int(page.Order), 168 OrderDirection: int(page.OrderDirection), 169 Search: page.Search, 170 CurrentPage: page.CurrentPage, 171 PageCount: page.PageCount, 172 } 173 return projectMembersPage, nil 174 }, 175 }, 176 FieldAPIKeys: &graphql.Field{ 177 Type: types.apiKeyPage, 178 Args: graphql.FieldConfigArgument{ 179 CursorArg: &graphql.ArgumentConfig{ 180 Type: graphql.NewNonNull(types.apiKeysCursor), 181 }, 182 }, 183 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 184 project, _ := p.Source.(*console.Project) 185 186 _, err := console.GetAuth(p.Context) 187 if err != nil { 188 return nil, err 189 } 190 191 cursor := cursorArgsToAPIKeysCursor(p.Args[CursorArg].(map[string]interface{})) 192 page, err := service.GetAPIKeys(p.Context, project.ID, cursor) 193 if err != nil { 194 return nil, err 195 } 196 197 apiKeysPage := apiKeysPage{ 198 APIKeys: page.APIKeys, 199 TotalCount: page.TotalCount, 200 Offset: page.Offset, 201 Limit: page.Limit, 202 Order: int(page.Order), 203 OrderDirection: int(page.OrderDirection), 204 Search: page.Search, 205 CurrentPage: page.CurrentPage, 206 PageCount: page.PageCount, 207 } 208 209 return apiKeysPage, err 210 }, 211 }, 212 FieldUsage: &graphql.Field{ 213 Type: types.projectUsage, 214 Args: graphql.FieldConfigArgument{ 215 SinceArg: &graphql.ArgumentConfig{ 216 Type: graphql.NewNonNull(graphql.DateTime), 217 }, 218 BeforeArg: &graphql.ArgumentConfig{ 219 Type: graphql.NewNonNull(graphql.DateTime), 220 }, 221 }, 222 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 223 project, _ := p.Source.(*console.Project) 224 225 since := p.Args[SinceArg].(time.Time) 226 before := p.Args[BeforeArg].(time.Time) 227 228 usage, err := service.GetProjectUsage(p.Context, project.ID, since, before) 229 if err != nil { 230 return nil, err 231 } 232 233 return usage, nil 234 }, 235 }, 236 FieldBucketUsages: &graphql.Field{ 237 Type: types.bucketUsagePage, 238 Args: graphql.FieldConfigArgument{ 239 BeforeArg: &graphql.ArgumentConfig{ 240 Type: graphql.NewNonNull(graphql.DateTime), 241 }, 242 CursorArg: &graphql.ArgumentConfig{ 243 Type: graphql.NewNonNull(types.bucketUsageCursor), 244 }, 245 }, 246 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 247 project, _ := p.Source.(*console.Project) 248 249 before := p.Args[BeforeArg].(time.Time) 250 cursor := fromMapBucketUsageCursor(p.Args[CursorArg].(map[string]interface{})) 251 252 page, err := service.GetBucketTotals(p.Context, project.ID, cursor, before) 253 if err != nil { 254 return nil, err 255 } 256 257 return page, nil 258 }, 259 }, 260 }, 261 }) 262} 263 264// graphqlProjectInput creates graphql.InputObject type needed to create/update satellite.Project. 265func graphqlProjectInput() *graphql.InputObject { 266 return graphql.NewInputObject(graphql.InputObjectConfig{ 267 Name: ProjectInputType, 268 Fields: graphql.InputObjectConfigFieldMap{ 269 FieldName: &graphql.InputObjectFieldConfig{ 270 Type: graphql.String, 271 }, 272 FieldDescription: &graphql.InputObjectFieldConfig{ 273 Type: graphql.String, 274 }, 275 }, 276 }) 277} 278 279// graphqlProjectLimit creates graphql.InputObject type needed to create/update satellite.Project. 280func graphqlProjectLimit() *graphql.InputObject { 281 return graphql.NewInputObject(graphql.InputObjectConfig{ 282 Name: ProjectLimitType, 283 Fields: graphql.InputObjectConfigFieldMap{ 284 FieldStorageLimit: &graphql.InputObjectFieldConfig{ 285 Type: graphql.String, 286 }, 287 FieldBandwidthLimit: &graphql.InputObjectFieldConfig{ 288 Type: graphql.String, 289 }, 290 }, 291 }) 292} 293 294// graphqlBucketUsageCursor creates bucket usage cursor graphql input type. 295func graphqlProjectsCursor() *graphql.InputObject { 296 return graphql.NewInputObject(graphql.InputObjectConfig{ 297 Name: ProjectsCursorInputType, 298 Fields: graphql.InputObjectConfigFieldMap{ 299 LimitArg: &graphql.InputObjectFieldConfig{ 300 Type: graphql.NewNonNull(graphql.Int), 301 }, 302 PageArg: &graphql.InputObjectFieldConfig{ 303 Type: graphql.NewNonNull(graphql.Int), 304 }, 305 }, 306 }) 307} 308 309// graphqlBucketUsageCursor creates bucket usage cursor graphql input type. 310func graphqlBucketUsageCursor() *graphql.InputObject { 311 return graphql.NewInputObject(graphql.InputObjectConfig{ 312 Name: BucketUsageCursorInputType, 313 Fields: graphql.InputObjectConfigFieldMap{ 314 SearchArg: &graphql.InputObjectFieldConfig{ 315 Type: graphql.NewNonNull(graphql.String), 316 }, 317 LimitArg: &graphql.InputObjectFieldConfig{ 318 Type: graphql.NewNonNull(graphql.Int), 319 }, 320 PageArg: &graphql.InputObjectFieldConfig{ 321 Type: graphql.NewNonNull(graphql.Int), 322 }, 323 }, 324 }) 325} 326 327// graphqlBucketUsage creates bucket usage grapqhl type. 328func graphqlBucketUsage() *graphql.Object { 329 return graphql.NewObject(graphql.ObjectConfig{ 330 Name: BucketUsageType, 331 Fields: graphql.Fields{ 332 FieldBucketName: &graphql.Field{ 333 Type: graphql.String, 334 }, 335 FieldStorage: &graphql.Field{ 336 Type: graphql.Float, 337 }, 338 FieldEgress: &graphql.Field{ 339 Type: graphql.Float, 340 }, 341 FieldObjectCount: &graphql.Field{ 342 Type: graphql.Float, 343 }, 344 FieldSegmentCount: &graphql.Field{ 345 Type: graphql.Float, 346 }, 347 SinceArg: &graphql.Field{ 348 Type: graphql.DateTime, 349 }, 350 BeforeArg: &graphql.Field{ 351 Type: graphql.DateTime, 352 }, 353 }, 354 }) 355} 356 357// graphqlProjectsPage creates a projects page graphql object. 358func graphqlProjectsPage(types *TypeCreator) *graphql.Object { 359 return graphql.NewObject(graphql.ObjectConfig{ 360 Name: ProjectsPageType, 361 Fields: graphql.Fields{ 362 FieldProjects: &graphql.Field{ 363 Type: graphql.NewList(types.project), 364 }, 365 LimitArg: &graphql.Field{ 366 Type: graphql.Int, 367 }, 368 OffsetArg: &graphql.Field{ 369 Type: graphql.Int, 370 }, 371 FieldPageCount: &graphql.Field{ 372 Type: graphql.Int, 373 }, 374 FieldCurrentPage: &graphql.Field{ 375 Type: graphql.Int, 376 }, 377 FieldTotalCount: &graphql.Field{ 378 Type: graphql.Int, 379 }, 380 }, 381 }) 382} 383 384// graphqlBucketUsagePage creates bucket usage page graphql object. 385func graphqlBucketUsagePage(types *TypeCreator) *graphql.Object { 386 return graphql.NewObject(graphql.ObjectConfig{ 387 Name: BucketUsagePageType, 388 Fields: graphql.Fields{ 389 FieldBucketUsages: &graphql.Field{ 390 Type: graphql.NewList(types.bucketUsage), 391 }, 392 SearchArg: &graphql.Field{ 393 Type: graphql.String, 394 }, 395 LimitArg: &graphql.Field{ 396 Type: graphql.Int, 397 }, 398 OffsetArg: &graphql.Field{ 399 Type: graphql.Int, 400 }, 401 FieldPageCount: &graphql.Field{ 402 Type: graphql.Int, 403 }, 404 FieldCurrentPage: &graphql.Field{ 405 Type: graphql.Int, 406 }, 407 FieldTotalCount: &graphql.Field{ 408 Type: graphql.Int, 409 }, 410 }, 411 }) 412} 413 414// graphqlProjectUsage creates project usage graphql type. 415func graphqlProjectUsage() *graphql.Object { 416 return graphql.NewObject(graphql.ObjectConfig{ 417 Name: ProjectUsageType, 418 Fields: graphql.Fields{ 419 FieldStorage: &graphql.Field{ 420 Type: graphql.Float, 421 }, 422 FieldEgress: &graphql.Field{ 423 Type: graphql.Float, 424 }, 425 FieldObjectCount: &graphql.Field{ 426 Type: graphql.Float, 427 }, 428 FieldSegmentCount: &graphql.Field{ 429 Type: graphql.Float, 430 }, 431 SinceArg: &graphql.Field{ 432 Type: graphql.DateTime, 433 }, 434 BeforeArg: &graphql.Field{ 435 Type: graphql.DateTime, 436 }, 437 }, 438 }) 439} 440 441// fromMapProjectInfo creates console.ProjectInfo from input args. 442func fromMapProjectInfo(args map[string]interface{}) (project console.ProjectInfo) { 443 project.Name, _ = args[FieldName].(string) 444 project.Description, _ = args[FieldDescription].(string) 445 446 return 447} 448 449// fromMapProjectInfoProjectLimits creates console.ProjectInfo from input args. 450func fromMapProjectInfoProjectLimits(projectInfo, projectLimits map[string]interface{}) (project console.ProjectInfo, err error) { 451 project.Name, _ = projectInfo[FieldName].(string) 452 project.Description, _ = projectInfo[FieldDescription].(string) 453 storageLimit, err := strconv.Atoi(projectLimits[FieldStorageLimit].(string)) 454 if err != nil { 455 return project, err 456 } 457 project.StorageLimit = memory.Size(storageLimit) 458 bandwidthLimit, err := strconv.Atoi(projectLimits[FieldBandwidthLimit].(string)) 459 if err != nil { 460 return project, err 461 } 462 project.BandwidthLimit = memory.Size(bandwidthLimit) 463 464 return 465} 466 467// fromMapProjectsCursor creates console.ProjectsCursor from input args. 468func fromMapProjectsCursor(args map[string]interface{}) (cursor console.ProjectsCursor) { 469 cursor.Limit = args[LimitArg].(int) 470 cursor.Page = args[PageArg].(int) 471 return 472} 473 474// fromMapBucketUsageCursor creates accounting.BucketUsageCursor from input args. 475func fromMapBucketUsageCursor(args map[string]interface{}) (cursor accounting.BucketUsageCursor) { 476 limit, _ := args[LimitArg].(int) 477 page, _ := args[PageArg].(int) 478 479 cursor.Limit = uint(limit) 480 cursor.Page = uint(page) 481 cursor.Search, _ = args[SearchArg].(string) 482 return 483} 484 485func cursorArgsToProjectMembersCursor(args map[string]interface{}) console.ProjectMembersCursor { 486 limit, _ := args[LimitArg].(int) 487 page, _ := args[PageArg].(int) 488 order, _ := args[OrderArg].(int) 489 orderDirection, _ := args[OrderDirectionArg].(int) 490 491 var cursor console.ProjectMembersCursor 492 493 cursor.Limit = uint(limit) 494 cursor.Page = uint(page) 495 cursor.Order = console.ProjectMemberOrder(order) 496 cursor.OrderDirection = console.OrderDirection(orderDirection) 497 cursor.Search, _ = args[SearchArg].(string) 498 499 return cursor 500} 501 502func cursorArgsToAPIKeysCursor(args map[string]interface{}) console.APIKeyCursor { 503 limit, _ := args[LimitArg].(int) 504 page, _ := args[PageArg].(int) 505 order, _ := args[OrderArg].(int) 506 orderDirection, _ := args[OrderDirectionArg].(int) 507 508 var cursor console.APIKeyCursor 509 510 cursor.Limit = uint(limit) 511 cursor.Page = uint(page) 512 cursor.Order = console.APIKeyOrder(order) 513 cursor.OrderDirection = console.OrderDirection(orderDirection) 514 cursor.Search, _ = args[SearchArg].(string) 515 516 return cursor 517} 518