stufflog graphql server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

717 lines
18 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. package resolvers
  2. // This file will be automatically regenerated based on the schema, any resolver implementations
  3. // will be copied through when generating and any unknown code will be moved to the end.
  4. import (
  5. "context"
  6. "errors"
  7. "log"
  8. "sort"
  9. "time"
  10. "git.aiterp.net/stufflog/server/graph/graphcore"
  11. "git.aiterp.net/stufflog/server/graph/loaders"
  12. "git.aiterp.net/stufflog/server/internal/generate"
  13. "git.aiterp.net/stufflog/server/internal/slerrors"
  14. "git.aiterp.net/stufflog/server/models"
  15. )
  16. func (r *mutationResolver) CreateProject(ctx context.Context, input graphcore.ProjectCreateInput) (*models.Project, error) {
  17. user := r.Auth.UserFromContext(ctx)
  18. if user == nil {
  19. return nil, slerrors.PermissionDenied
  20. }
  21. project := &models.Project{
  22. ID: input.ID,
  23. Name: input.Name,
  24. Description: input.Description,
  25. DailyPoints: input.DailyPoints,
  26. }
  27. if !project.ValidKey() {
  28. return nil, errors.New("the project must have a valid ID (only uppercase allowed)")
  29. }
  30. if project.Name == "" || project.Description == "" {
  31. return nil, errors.New("name and description cannot be left empty")
  32. }
  33. project, err := r.Database.Projects().Insert(ctx, *project)
  34. if err != nil {
  35. return nil, err
  36. }
  37. err = r.Database.Projects().SetPermission(ctx, models.ProjectPermission{
  38. ProjectID: project.ID,
  39. UserID: user.ID,
  40. Level: models.ProjectPermissionLevelOwner,
  41. })
  42. return project, nil
  43. }
  44. func (r *mutationResolver) CreateActivity(ctx context.Context, input graphcore.ActivityCreateInput) (*models.Activity, error) {
  45. user := r.Auth.UserFromContext(ctx)
  46. if user == nil {
  47. return nil, slerrors.PermissionDenied
  48. }
  49. project, err := r.Database.Projects().Find(ctx, input.ProjectID)
  50. if err != nil {
  51. return nil, err
  52. }
  53. if perm, err := r.Auth.ProjectPermission(ctx, project.ID); err != nil || !perm.CanManageActivities() {
  54. return nil, slerrors.PermissionDenied
  55. }
  56. activity := &models.Activity{
  57. ProjectID: project.ID,
  58. Name: input.Name,
  59. Countable: input.Countable != nil && *input.Countable,
  60. UnitIsTimeSpent: input.UnitIsTimeSpent != nil && *input.UnitIsTimeSpent,
  61. BaseValue: input.BaseValue,
  62. }
  63. if !activity.UnitIsTimeSpent && activity.Countable {
  64. if input.UnitName != nil {
  65. activity.UnitName = *input.UnitName
  66. } else {
  67. return nil, errors.New("unit name is required for countable non-time-spent activities")
  68. }
  69. }
  70. if activity.UnitIsTimeSpent || activity.Countable {
  71. if input.UnitValue != nil {
  72. activity.UnitValue = *input.UnitValue
  73. }
  74. }
  75. return r.Database.Activities().Insert(ctx, *activity)
  76. }
  77. func (r *mutationResolver) EditActivity(ctx context.Context, input graphcore.ActivityEditInput) (*models.Activity, error) {
  78. user := r.Auth.UserFromContext(ctx)
  79. if user == nil {
  80. return nil, slerrors.PermissionDenied
  81. }
  82. activity, err := r.Database.Activities().Find(ctx, input.ActivityID)
  83. if err != nil {
  84. return nil, err
  85. }
  86. project, err := r.Database.Projects().Find(ctx, activity.ProjectID)
  87. if err != nil {
  88. return nil, err
  89. }
  90. if perm, err := r.Auth.ProjectPermission(ctx, project.ID); err != nil || !perm.CanManageActivities() {
  91. return nil, slerrors.PermissionDenied
  92. }
  93. if input.SetName != nil {
  94. activity.Name = *input.SetName
  95. }
  96. if input.SetBaseValue != nil {
  97. activity.BaseValue = *input.SetBaseValue
  98. }
  99. if input.SetUnitName != nil {
  100. activity.UnitName = *input.SetUnitName
  101. }
  102. if input.SetUnitValue != nil {
  103. activity.UnitValue = *input.SetUnitValue
  104. }
  105. err = r.Database.Activities().Save(ctx, *activity)
  106. if err != nil {
  107. return nil, err
  108. }
  109. return activity, nil
  110. }
  111. func (r *mutationResolver) CreateItem(ctx context.Context, input *graphcore.ItemCreateInput) (*models.Item, error) {
  112. user := r.Auth.UserFromContext(ctx)
  113. if user == nil {
  114. return nil, slerrors.PermissionDenied
  115. }
  116. if input.Name == "" {
  117. return nil, errors.New("name cannot be blank")
  118. }
  119. if input.Description == "" {
  120. return nil, errors.New("name cannot be blank")
  121. }
  122. if len(input.Tags) == 0 {
  123. return nil, errors.New("at least one tag is required")
  124. }
  125. item := models.Item{
  126. Name: input.Name,
  127. Description: input.Description,
  128. Tags: input.Tags,
  129. QuantityUnit: input.QuantityUnit,
  130. ImageURL: nil,
  131. }
  132. if input.Image != nil {
  133. url, err := r.Upload.UploadImage(
  134. ctx,
  135. "item-image/"+generate.Generate(16, "I"),
  136. input.Image.ContentType,
  137. input.Image.Size,
  138. input.Image.File,
  139. )
  140. if err != nil {
  141. return nil, err
  142. }
  143. item.ImageURL = &url
  144. }
  145. return r.Database.Items().Insert(ctx, item)
  146. }
  147. func (r *mutationResolver) EditItem(ctx context.Context, input *graphcore.ItemEditInput) (*models.Item, error) {
  148. user := r.Auth.UserFromContext(ctx)
  149. if user == nil {
  150. return nil, slerrors.PermissionDenied
  151. }
  152. item, err := r.Database.Items().Find(ctx, input.ItemID)
  153. if err != nil {
  154. return nil, err
  155. }
  156. deleteList := make([]int, 0, len(input.RemoveTags))
  157. for _, tag := range input.RemoveTags {
  158. for i, tag2 := range item.Tags {
  159. if tag == tag2 {
  160. deleteList = append(deleteList, i-len(deleteList))
  161. break
  162. }
  163. }
  164. }
  165. for _, index := range deleteList {
  166. item.Tags = append(item.Tags[:index], item.Tags[index+1:]...)
  167. }
  168. for _, tag := range input.AddTags {
  169. found := false
  170. for _, tag2 := range item.Tags {
  171. if tag == tag2 {
  172. found = true
  173. break
  174. }
  175. }
  176. if !found {
  177. item.Tags = append(item.Tags, tag)
  178. }
  179. }
  180. sort.Strings(item.Tags)
  181. if input.SetQuantityUnit != nil {
  182. item.QuantityUnit = input.SetQuantityUnit
  183. }
  184. if input.ClearQuantityUnit != nil && *input.ClearQuantityUnit {
  185. item.QuantityUnit = nil
  186. }
  187. if input.SetName != nil {
  188. item.Name = *input.SetName
  189. }
  190. if input.SetDescription != nil {
  191. item.Description = *input.SetDescription
  192. }
  193. var prevFile *string
  194. if input.UpdateImage != nil {
  195. url, err := r.Upload.UploadImage(
  196. ctx,
  197. "item-image/"+generate.Generate(16, "U"),
  198. input.UpdateImage.ContentType,
  199. input.UpdateImage.Size,
  200. input.UpdateImage.File,
  201. )
  202. if err != nil {
  203. return nil, err
  204. }
  205. prevFile = item.ImageURL
  206. item.ImageURL = &url
  207. }
  208. err = r.Database.Items().Save(ctx, *item)
  209. if err != nil {
  210. return nil, err
  211. }
  212. if prevFile != nil {
  213. err := r.Upload.Delete(ctx, *prevFile)
  214. if err != nil {
  215. log.Printf("Failed to delete %s: %s", *prevFile, err)
  216. }
  217. }
  218. return item, nil
  219. }
  220. func (r *mutationResolver) CreateIssue(ctx context.Context, input graphcore.IssueCreateInput) (*models.Issue, error) {
  221. user := r.Auth.UserFromContext(ctx)
  222. if user == nil {
  223. return nil, slerrors.PermissionDenied
  224. }
  225. project, err := r.Database.Projects().Find(ctx, input.ProjectID)
  226. if err != nil {
  227. return nil, err
  228. }
  229. if perm, err := r.Auth.ProjectPermission(ctx, project.ID); err != nil || !perm.CanManageOwnIssue() {
  230. return nil, slerrors.PermissionDenied
  231. }
  232. status, err := r.Database.ProjectStatuses().Find(ctx, project.ID, input.StatusName)
  233. if err != nil {
  234. return nil, err
  235. }
  236. issue := &models.Issue{
  237. ProjectID: project.ID,
  238. OwnerID: user.ID,
  239. AssigneeID: "",
  240. StatusStage: status.Stage,
  241. StatusName: status.Name,
  242. Name: input.Name,
  243. Title: input.Name, // Title set below if it's in the input.
  244. Description: input.Description,
  245. }
  246. if input.Title != nil && *input.Title != "" {
  247. issue.Title = *input.Title
  248. }
  249. if input.DueTime != nil && !input.DueTime.IsZero() {
  250. issue.DueTime = *input.DueTime
  251. }
  252. if input.AssigneeID != nil && *input.AssigneeID != "" {
  253. issue.AssigneeID = *input.AssigneeID
  254. }
  255. issue, err = r.Database.Issues().Insert(ctx, *issue)
  256. if err != nil {
  257. return nil, err
  258. }
  259. loaders.IssueLoaderFromContext(ctx).Prime(issue.ID, issue)
  260. return issue, nil
  261. }
  262. func (r *mutationResolver) EditIssue(ctx context.Context, input graphcore.IssueEditInput) (*models.Issue, error) {
  263. user := r.Auth.UserFromContext(ctx)
  264. if user == nil {
  265. return nil, slerrors.PermissionDenied
  266. }
  267. issue, err := loaders.IssueLoaderFromContext(ctx).Load(input.IssueID)
  268. if err != nil {
  269. return nil, err
  270. }
  271. if perm, err := r.Auth.IssuePermission(ctx, *issue); err != nil || !perm.CanManageOwnIssue() {
  272. return nil, slerrors.PermissionDenied
  273. }
  274. if input.SetAssignee != nil {
  275. if *input.SetAssignee != "" {
  276. assignee, err := loaders.UserLoaderFromContext(ctx).Load(*input.SetAssignee)
  277. if err != nil {
  278. return nil, err
  279. }
  280. if perm, err := r.Auth.IssuePermission(ctx, *issue); err != nil || !perm.CanManageOwnIssue() {
  281. return nil, slerrors.Forbidden("Cannot assign to user who cannot manage their own issues.")
  282. }
  283. issue.AssigneeID = assignee.ID
  284. } else {
  285. issue.AssigneeID = ""
  286. }
  287. }
  288. if input.SetName != nil {
  289. issue.Name = *input.SetName
  290. }
  291. if input.SetTitle != nil {
  292. issue.Name = *input.SetTitle
  293. }
  294. if input.SetDueTime != nil {
  295. issue.DueTime = *input.SetDueTime
  296. }
  297. if input.SetStatusName != nil {
  298. status, err := r.Database.ProjectStatuses().Find(ctx, issue.ProjectID, *input.SetStatusName)
  299. if err != nil {
  300. return nil, err
  301. }
  302. issue.StatusName = status.Name
  303. issue.StatusStage = status.Stage
  304. }
  305. err = r.Database.Issues().Save(ctx, *issue)
  306. if err != nil {
  307. return nil, err
  308. }
  309. return issue, nil
  310. }
  311. func (r *mutationResolver) CreateIssueTask(ctx context.Context, input graphcore.IssueTaskCreateInput) (*models.IssueTask, error) {
  312. user := r.Auth.UserFromContext(ctx)
  313. if user == nil {
  314. return nil, slerrors.PermissionDenied
  315. }
  316. issue, err := loaders.IssueLoaderFromContext(ctx).Load(input.IssueID)
  317. if err != nil {
  318. return nil, err
  319. }
  320. if perm, err := r.Auth.IssuePermission(ctx, *issue); err != nil || !perm.CanManageOwnIssue() {
  321. return nil, slerrors.PermissionDenied
  322. }
  323. status, err := r.Database.ProjectStatuses().Find(ctx, issue.ProjectID, input.StatusName)
  324. if err != nil {
  325. return nil, err
  326. }
  327. activity, err := loaders.ActivityLoaderFromContext(ctx).Load(input.ActivityID)
  328. if err != nil {
  329. return nil, err
  330. } else if activity.ProjectID != issue.ProjectID {
  331. return nil, slerrors.NotFound("Activity")
  332. }
  333. issueTask := &models.IssueTask{
  334. IssueID: issue.ID,
  335. ActivityID: activity.ID,
  336. CreatedTime: time.Now(),
  337. UpdatedTime: time.Now(),
  338. StatusStage: status.Stage,
  339. StatusName: status.Name,
  340. Name: input.Name,
  341. Description: input.Description,
  342. EstimatedTime: input.EstimatedTime,
  343. PointsMultiplier: 1.0,
  344. }
  345. if input.EstimatedUnits != nil && activity.Countable && !activity.UnitIsTimeSpent {
  346. issueTask.EstimatedUnits = *input.EstimatedUnits
  347. }
  348. if input.PointsMultiplier != nil && *input.PointsMultiplier > 0 {
  349. issueTask.PointsMultiplier = *input.PointsMultiplier
  350. }
  351. issueTask, err = r.Database.IssueTasks().Insert(ctx, *issueTask)
  352. if err != nil {
  353. return nil, err
  354. }
  355. return issueTask, nil
  356. }
  357. func (r *mutationResolver) EditIssueTask(ctx context.Context, input graphcore.IssueTaskEditInput) (*models.IssueTask, error) {
  358. user := r.Auth.UserFromContext(ctx)
  359. if user == nil {
  360. return nil, slerrors.PermissionDenied
  361. }
  362. task, err := r.Database.IssueTasks().Find(ctx, input.IssueTaskID)
  363. if err != nil {
  364. return nil, err
  365. }
  366. issue, err := loaders.IssueLoaderFromContext(ctx).Load(task.IssueID)
  367. if err != nil {
  368. return nil, err
  369. }
  370. if perm, err := r.Auth.IssuePermission(ctx, *issue); err != nil || !perm.CanManageOwnIssue() {
  371. return nil, slerrors.PermissionDenied
  372. }
  373. if input.SetName != nil {
  374. task.Name = *input.SetName
  375. }
  376. if input.SetDescription != nil {
  377. task.Description = *input.SetDescription
  378. }
  379. if input.SetDueTime != nil {
  380. task.DueTime = input.SetDueTime
  381. }
  382. if input.SetEstimatedTime != nil {
  383. task.EstimatedTime = *input.SetEstimatedTime
  384. }
  385. if input.SetEstimatedUnits != nil {
  386. activity, err := loaders.ActivityLoaderFromContext(ctx).Load(task.ActivityID)
  387. if err != nil {
  388. return nil, err
  389. }
  390. if activity.Countable && !activity.UnitIsTimeSpent {
  391. task.EstimatedUnits = *input.SetEstimatedUnits
  392. }
  393. }
  394. if input.SetPointsMultiplier != nil && *input.SetPointsMultiplier > 0 {
  395. task.PointsMultiplier = *input.SetPointsMultiplier
  396. }
  397. if input.SetStatusName != nil {
  398. status, err := r.Database.ProjectStatuses().Find(ctx, issue.ProjectID, *input.SetStatusName)
  399. if err != nil {
  400. return nil, err
  401. }
  402. task.StatusName = status.Name
  403. task.StatusStage = status.Stage
  404. }
  405. err = r.Database.IssueTasks().Save(ctx, *task)
  406. if err != nil {
  407. return nil, err
  408. }
  409. return task, nil
  410. }
  411. func (r *mutationResolver) CreateIssueItem(ctx context.Context, input graphcore.IssueItemCreateInput) (*models.IssueItem, error) {
  412. user := r.Auth.UserFromContext(ctx)
  413. if user == nil {
  414. return nil, slerrors.PermissionDenied
  415. }
  416. issue, err := loaders.IssueLoaderFromContext(ctx).Load(input.IssueID)
  417. if err != nil {
  418. return nil, err
  419. }
  420. if perm, err := r.Auth.IssuePermission(ctx, *issue); err != nil || !perm.CanManageOwnIssue() {
  421. return nil, slerrors.PermissionDenied
  422. }
  423. item, err := r.Database.Items().Find(ctx, input.ItemID)
  424. if err != nil {
  425. return nil, err
  426. }
  427. issueItem := &models.IssueItem{
  428. IssueID: issue.ID,
  429. ItemID: item.ID,
  430. Quantity: input.Quanitty,
  431. Acquired: input.Acquired != nil && *input.Acquired,
  432. }
  433. return r.Database.IssueItems().Insert(ctx, *issueItem)
  434. }
  435. func (r *mutationResolver) EditIssueItem(ctx context.Context, input graphcore.IssueItemEditInput) (*models.IssueItem, error) {
  436. user := r.Auth.UserFromContext(ctx)
  437. if user == nil {
  438. return nil, slerrors.PermissionDenied
  439. }
  440. item, err := r.Database.IssueItems().Find(ctx, input.IssueItemID)
  441. if err != nil {
  442. return nil, err
  443. }
  444. issue, err := loaders.IssueLoaderFromContext(ctx).Load(item.IssueID)
  445. if err != nil {
  446. return nil, err
  447. }
  448. if perm, err := r.Auth.IssuePermission(ctx, *issue); err != nil || !perm.CanManageOwnIssue() {
  449. return nil, slerrors.PermissionDenied
  450. }
  451. if input.SetAcquired != nil {
  452. item.Acquired = *input.SetAcquired
  453. }
  454. if input.SetQuanitty != nil {
  455. item.Quantity = *input.SetQuanitty
  456. }
  457. err = r.Database.IssueItems().Save(ctx, *item)
  458. if err != nil {
  459. return nil, err
  460. }
  461. return item, nil
  462. }
  463. func (r *mutationResolver) CreateLog(ctx context.Context, input graphcore.LogCreateInput) (*models.Log, error) {
  464. user := r.Auth.UserFromContext(ctx)
  465. if user == nil {
  466. return nil, slerrors.PermissionDenied
  467. }
  468. log := &models.Log{
  469. UserID: user.ID,
  470. Date: input.Date,
  471. Description: input.Description,
  472. Items: make([]models.LogItem, 0, len(input.Items)),
  473. Tasks: make([]models.LogTask, 0, len(input.Tasks)),
  474. }
  475. for _, itemInput := range input.Items {
  476. item, err := r.Database.IssueItems().Find(ctx, itemInput.IssueItemID)
  477. if err != nil {
  478. return nil, err
  479. }
  480. issue, err := loaders.IssueLoaderFromContext(ctx).Load(item.IssueID)
  481. if err != nil {
  482. return nil, err
  483. }
  484. _, err = r.Auth.IssuePermission(ctx, *issue)
  485. if err != nil {
  486. return nil, err
  487. }
  488. log.Items = append(log.Items, models.LogItem{
  489. IssueID: item.IssueID,
  490. IssueItemID: item.ID,
  491. Amount: itemInput.Amount,
  492. })
  493. }
  494. for _, taskInput := range input.Tasks {
  495. task, err := r.Database.IssueTasks().Find(ctx, taskInput.IssueTaskID)
  496. if err != nil {
  497. return nil, err
  498. }
  499. issue, err := loaders.IssueLoaderFromContext(ctx).Load(task.IssueID)
  500. if err != nil {
  501. return nil, err
  502. }
  503. _, err = r.Auth.IssuePermission(ctx, *issue)
  504. if err != nil {
  505. return nil, err
  506. }
  507. log.Tasks = append(log.Tasks, models.LogTask{
  508. IssueID: task.IssueID,
  509. IssueTaskID: task.ID,
  510. Duration: taskInput.Duration,
  511. Units: taskInput.Units,
  512. })
  513. }
  514. return r.Database.Logs().Insert(ctx, *log)
  515. }
  516. func (r *mutationResolver) EditLog(ctx context.Context, input graphcore.LogEditInput) (*models.Log, error) {
  517. user := r.Auth.UserFromContext(ctx)
  518. if user == nil {
  519. return nil, slerrors.PermissionDenied
  520. }
  521. log, err := r.Database.Logs().Find(ctx, input.LogID)
  522. if err != nil {
  523. return nil, err
  524. }
  525. removeItems := append([]string{}, input.RemoveItems...)
  526. for _, itemInput := range input.UpdateItems {
  527. removeItems = append(removeItems, itemInput.IssueItemID)
  528. }
  529. removeTasks := append([]string{}, input.RemoveTasks...)
  530. for _, taskInput := range input.UpdateTasks {
  531. removeTasks = append(removeTasks, taskInput.IssueTaskID)
  532. }
  533. if input.SetDate != nil {
  534. log.Date = *input.SetDate
  535. }
  536. if input.SetDescription != nil {
  537. log.Description = *input.SetDescription
  538. }
  539. for _, remove := range removeItems {
  540. for i, item := range log.Items {
  541. if remove == item.IssueItemID {
  542. log.Items = append(log.Items[:i], log.Items[i+1:]...)
  543. }
  544. }
  545. }
  546. for _, remove := range removeTasks {
  547. for i, task := range log.Tasks {
  548. if remove == task.IssueTaskID {
  549. log.Tasks = append(log.Tasks[:i], log.Tasks[i+1:]...)
  550. }
  551. }
  552. }
  553. for _, itemInput := range input.UpdateItems {
  554. item, err := r.Database.IssueItems().Find(ctx, itemInput.IssueItemID)
  555. if err != nil {
  556. return nil, err
  557. }
  558. issue, err := loaders.IssueLoaderFromContext(ctx).Load(item.IssueID)
  559. if err != nil {
  560. return nil, err
  561. }
  562. _, err = r.Auth.IssuePermission(ctx, *issue)
  563. if err != nil {
  564. return nil, err
  565. }
  566. log.Items = append(log.Items, models.LogItem{
  567. LogID: log.ID,
  568. IssueID: item.IssueID,
  569. IssueItemID: item.ID,
  570. Amount: itemInput.Amount,
  571. })
  572. }
  573. for _, taskInput := range input.UpdateTasks {
  574. task, err := r.Database.IssueTasks().Find(ctx, taskInput.IssueTaskID)
  575. if err != nil {
  576. return nil, err
  577. }
  578. issue, err := loaders.IssueLoaderFromContext(ctx).Load(task.IssueID)
  579. if err != nil {
  580. return nil, err
  581. }
  582. _, err = r.Auth.IssuePermission(ctx, *issue)
  583. if err != nil {
  584. return nil, err
  585. }
  586. log.Tasks = append(log.Tasks, models.LogTask{
  587. LogID: log.ID,
  588. IssueID: task.IssueID,
  589. IssueTaskID: task.ID,
  590. Duration: taskInput.Duration,
  591. Units: taskInput.Units,
  592. })
  593. }
  594. err = r.Database.Logs().Save(ctx, *log)
  595. if err != nil {
  596. return nil, err
  597. }
  598. return log, nil
  599. }
  600. func (r *mutationResolver) LoginUser(ctx context.Context, input graphcore.UserLoginInput) (*models.User, error) {
  601. return r.Auth.Login(ctx, input.Username, input.Password)
  602. }
  603. func (r *mutationResolver) LogoutUser(ctx context.Context) (*models.User, error) {
  604. return r.Auth.Logout(ctx)
  605. }
  606. func (r *mutationResolver) CreateUser(ctx context.Context, input graphcore.UserCreateInput) (*models.User, error) {
  607. active := input.Active == nil || *input.Active
  608. admin := input.Admin != nil && *input.Admin
  609. return r.Auth.CreateUser(ctx, input.Username, input.Password, input.Name, active, admin)
  610. }
  611. func (r *mutationResolver) EditUser(ctx context.Context, input graphcore.UserEditInput) (*models.User, error) {
  612. return r.Auth.EditUser(ctx, input.Username, input.SetName, input.CurrentPassword, input.SetPassword)
  613. }
  614. // Mutation returns graphcore.MutationResolver implementation.
  615. func (r *Resolver) Mutation() graphcore.MutationResolver { return &mutationResolver{r} }
  616. type mutationResolver struct{ *Resolver }