From 41702cb9e3830144473a7329b984f0b5cf121acb Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sun, 12 Jul 2020 18:30:02 +0200 Subject: [PATCH 01/25] Implement mutations --- schema/api.graphql | 26 +++++++++- src/Controllers/api/graphql.php | 91 ++++++++++++++++++--------------- 2 files changed, 76 insertions(+), 41 deletions(-) diff --git a/schema/api.graphql b/schema/api.graphql index d6ba9d9aa..9e39f1e70 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -18,7 +18,8 @@ directive @bind( ) on FIELD_DEFINITION | OBJECT enum CallingConvention { - FirstArgCurrentUser + FirstArgCurrentUser, + FirstArgInput } enum AuthHook { @@ -347,6 +348,29 @@ type Query { mailingListByName(name: String!): MailingList @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_List", method: "getByName") } +type Mutation { + createShow(input: CreateShowInput): Show @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Show", method: "create", callingConvention: FirstArgInput) +} + +input ShowCreditInput { # this is icky + memberid: [Int!]! + credittype: [Int!]! +} + +input CreateShowInput { + name: String! + description: HTMLString! + credits: ShowCreditInput! + genres: [Int!] + tags: String + podcast_explicit: Boolean + subtype: String! # TODO: not a string + mixclouder: Boolean + "Unused" + location: Int # unused +} + schema { query: Query + mutation: Mutation } diff --git a/src/Controllers/api/graphql.php b/src/Controllers/api/graphql.php index c32a9507d..e2e3ec2db 100644 --- a/src/Controllers/api/graphql.php +++ b/src/Controllers/api/graphql.php @@ -79,47 +79,7 @@ ] ); }; - - $typeConfig['resolveField'] = function ($source, $args, GraphQLContext $context, ResolveInfo $info) { - $fieldName = $info->fieldName; - // If we're on the Query type, we're entering the graph, so we'll want a static method. - // Unlike elsewhere in the graph, we can assume everything on Query will have an @bind. - $bindDirective = GraphQLUtils::getDirectiveByName($info, 'bind'); - if (!$bindDirective) { - throw new MyRadioException("Tried to resolve $fieldName on Query but it didn't have an @bind"); - } - $bindArgs = GraphQLUtils::getDirectiveArguments($bindDirective); - if (isset($bindArgs['class'])) { - // we know class is a string - /** @noinspection PhpPossiblePolymorphicInvocationInspection */ - $className = $bindArgs['class']->value; - } else { - throw new MyRadioException( - "Tried to resolve $fieldName on Query but its @bind didn't have a class" - ); - } - if (isset($bindArgs['method'])) { - $methodName = $bindArgs['method']->value; - } else { - throw new MyRadioException( - "Tried to resolve $fieldName on Query but its @bind didn't have a method" - ); - } - // Wonderful! - $clazz = new ReflectionClass($className); - $meth = $clazz->getMethod($methodName); - if (GraphQLUtils::isAuthorisedToAccess($info, $className, $methodName)) { - return GraphQLUtils::processScalarIfNecessary( - $info, - GraphQLUtils::invokeNamed($meth, null, $args) - ); - } else { - return GraphQLUtils::returnNullOrThrowForbiddenException($info); - } - }; break; - case "Mutation": - throw new MyRadioException('Mutations not supported'); } return $typeConfig; }; @@ -137,6 +97,57 @@ function graphQlResolver($source, $args, GraphQLContext $context, ResolveInfo $i { $typeName = $info->parentType->name; $fieldName = $info->fieldName; + // Query and Mutation deserve special handling + if ($typeName === 'Query' || $typeName === 'Mutation') { + // If we're on the Query type, we're entering the graph, so we'll want a static method. + // Unlike elsewhere in the graph, we can assume everything on Query will have an @bind. + $bindDirective = GraphQLUtils::getDirectiveByName($info, 'bind'); + if (!$bindDirective) { + throw new MyRadioException("Tried to resolve $fieldName on Query but it didn't have an @bind"); + } + $bindArgs = GraphQLUtils::getDirectiveArguments($bindDirective); + if (isset($bindArgs['class'])) { + // we know class is a string + /** @noinspection PhpPossiblePolymorphicInvocationInspection */ + $className = $bindArgs['class']->value; + } else { + throw new MyRadioException( + "Tried to resolve $fieldName on Query but its @bind didn't have a class" + ); + } + if (isset($bindArgs['method'])) { + $methodName = $bindArgs['method']->value; + } else { + throw new MyRadioException( + "Tried to resolve $fieldName on Query but its @bind didn't have a method" + ); + } + // Wonderful! + $clazz = new ReflectionClass($className); + // (We'll get a ReflectionException here if it's inaccessible. But That's Okay. + $meth = $clazz->getMethod($methodName); + if (GraphQLUtils::isAuthorisedToAccess($info, $className, $methodName)) { + // First, though, check if we should be using a calling convention + if (isset($bindArgs['callingConvention'])) { + $cc = $bindArgs['callingConvention']; + switch ($cc) { + case 'FirstArgCurrentUser': + $val = $source->{$methodName}(MyRadio_User::getInstance()->getID()); + break; + case 'FirstArgInput': + $val = $source->{$methodName}($args['input']); + break; + default: + throw new MyRadioException("Unknown calling convention $cc"); + } + } else { + $val = GraphQLUtils::invokeNamed($meth, $source, $args); + } + return GraphQLUtils::processScalarIfNecessary($info, $val); + } else { + return GraphQLUtils::returnNullOrThrowForbiddenException($info); + } + } // First up, check if we have a bind directive $bindDirective = GraphQLUtils::getDirectiveByName($info, 'bind'); if ($bindDirective) { From b5135b7d9c4e6c01ea727008ad0e4d4d6299f0de Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sun, 12 Jul 2020 18:32:38 +0200 Subject: [PATCH 02/25] oops --- src/Controllers/api/graphql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/api/graphql.php b/src/Controllers/api/graphql.php index e2e3ec2db..0081c5be5 100644 --- a/src/Controllers/api/graphql.php +++ b/src/Controllers/api/graphql.php @@ -129,7 +129,7 @@ function graphQlResolver($source, $args, GraphQLContext $context, ResolveInfo $i if (GraphQLUtils::isAuthorisedToAccess($info, $className, $methodName)) { // First, though, check if we should be using a calling convention if (isset($bindArgs['callingConvention'])) { - $cc = $bindArgs['callingConvention']; + $cc = $bindArgs['callingConvention']->value; switch ($cc) { case 'FirstArgCurrentUser': $val = $source->{$methodName}(MyRadio_User::getInstance()->getID()); From 353c60202ee2bb1069289dbc1195908aa3a4ae3d Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sun, 12 Jul 2020 18:33:50 +0200 Subject: [PATCH 03/25] oops 2 --- src/Controllers/api/graphql.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Controllers/api/graphql.php b/src/Controllers/api/graphql.php index 0081c5be5..063c9b3e8 100644 --- a/src/Controllers/api/graphql.php +++ b/src/Controllers/api/graphql.php @@ -132,10 +132,10 @@ function graphQlResolver($source, $args, GraphQLContext $context, ResolveInfo $i $cc = $bindArgs['callingConvention']->value; switch ($cc) { case 'FirstArgCurrentUser': - $val = $source->{$methodName}(MyRadio_User::getInstance()->getID()); + $val = $meth->invokeArgs(null, [MyRadio_User::getInstance()->getID()]); break; case 'FirstArgInput': - $val = $source->{$methodName}($args['input']); + $val = $meth->invokeArgs(null, $args['input']); break; default: throw new MyRadioException("Unknown calling convention $cc"); From a806ee670945a3d1e2c23904c9e8b54c31312620 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sun, 12 Jul 2020 18:34:17 +0200 Subject: [PATCH 04/25] oops 3 --- schema/api.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/api.graphql b/schema/api.graphql index 9e39f1e70..5bd2f724e 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -358,7 +358,7 @@ input ShowCreditInput { # this is icky } input CreateShowInput { - name: String! + title: String! description: HTMLString! credits: ShowCreditInput! genres: [Int!] From 038d60be2b7953170b0923520b50a366aed02358 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sun, 12 Jul 2020 18:36:51 +0200 Subject: [PATCH 05/25] oops 4 --- schema/api.graphql | 2 +- src/Controllers/api/graphql.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schema/api.graphql b/schema/api.graphql index 5bd2f724e..b3a0e2107 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -367,7 +367,7 @@ input CreateShowInput { subtype: String! # TODO: not a string mixclouder: Boolean "Unused" - location: Int # unused + location: Int } schema { diff --git a/src/Controllers/api/graphql.php b/src/Controllers/api/graphql.php index 063c9b3e8..825d3a053 100644 --- a/src/Controllers/api/graphql.php +++ b/src/Controllers/api/graphql.php @@ -135,7 +135,7 @@ function graphQlResolver($source, $args, GraphQLContext $context, ResolveInfo $i $val = $meth->invokeArgs(null, [MyRadio_User::getInstance()->getID()]); break; case 'FirstArgInput': - $val = $meth->invokeArgs(null, $args['input']); + $val = $meth->invokeArgs(null, [$args['input']]); break; default: throw new MyRadioException("Unknown calling convention $cc"); From 2e4962a308102d4b0002e8928cf4236b89dd99dd Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 13 Jul 2020 13:42:52 +0200 Subject: [PATCH 06/25] MyRadio_Show::create: allow tags to be an array --- schema/api.graphql | 30 +++++++++++++++++++++++++ src/Classes/ServiceAPI/MyRadio_Show.php | 7 ++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/schema/api.graphql b/schema/api.graphql index b3a0e2107..63ccc3457 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -370,6 +370,36 @@ input CreateShowInput { location: Int } +input CreateSeasonWeeks { + wk1: Boolean! + wk2: Boolean! + wk3: Boolean! + wk4: Boolean! + wk5: Boolean! + wk6: Boolean! + wk7: Boolean! + wk8: Boolean! + wk9: Boolean! + wk10: Boolean! +} + +input CreateSeasonTimes { + day: [Int!]! + "Seconds since midnight" + stime: [Int!]! + "Seconds since midnight" + etime: [Int!]! +} + +input CreateSeasonInput { + show_id: Int! + weeks: CreateSeasonWeeks! + times: CreateSeasonTimes! + tags: [String] + description: HTMLString + subtype: String +} + schema { query: Query mutation: Mutation diff --git a/src/Classes/ServiceAPI/MyRadio_Show.php b/src/Classes/ServiceAPI/MyRadio_Show.php index 6fa986b51..e2c64a1d2 100755 --- a/src/Classes/ServiceAPI/MyRadio_Show.php +++ b/src/Classes/ServiceAPI/MyRadio_Show.php @@ -253,7 +253,7 @@ public static function create($params = []) $params['genres'] = []; } if (!isset($params['tags'])) { - $params['tags'] = ''; + $params['tags'] = []; } // Support API calls where there is no session. @@ -312,7 +312,10 @@ public static function create($params = []) } // Explode the tags - $tags = CoreUtils::explodeTags($params['tags']); + $tags = $params['tags']; + if(!(is_array($tags))) { + $tags = CoreUtils::explodeTags($params['tags']); + } foreach ($tags as $tag) { self::$db->query( 'INSERT INTO schedule.show_metadata From 03793ae7dfefc3c30ec33342faf54bf472bdb321 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 13 Jul 2020 13:45:14 +0200 Subject: [PATCH 07/25] oops --- schema/api.graphql | 2 +- src/Classes/ServiceAPI/MyRadio_Season.php | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/schema/api.graphql b/schema/api.graphql index 63ccc3457..3cf124fe7 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -362,7 +362,7 @@ input CreateShowInput { description: HTMLString! credits: ShowCreditInput! genres: [Int!] - tags: String + tags: [String] podcast_explicit: Boolean subtype: String! # TODO: not a string mixclouder: Boolean diff --git a/src/Classes/ServiceAPI/MyRadio_Season.php b/src/Classes/ServiceAPI/MyRadio_Season.php index b24aa7c50..8be3f85cc 100644 --- a/src/Classes/ServiceAPI/MyRadio_Season.php +++ b/src/Classes/ServiceAPI/MyRadio_Season.php @@ -180,7 +180,13 @@ public static function create($params = []) throw new MyRadioException('Parameter '.$field.' was not provided.', 400); } } - $tags = (!empty($params['tags'])) ? CoreUtils::explodeTags($params['tags']) : []; + if (empty($params['tags'])) { + $tags = []; + } else if (is_array($params['tags'])) { + $tags = $params['tags']; + } else { + $tags = CoreUtils::explodeTags($params['tags']); + } /** * Select an appropriate value for $term_id. From f4fd5c1b93873bead4871cc4cfc039ba28ec0b03 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 13 Jul 2020 13:45:59 +0200 Subject: [PATCH 08/25] nulls bad mmkay --- schema/api.graphql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schema/api.graphql b/schema/api.graphql index 3cf124fe7..8f24657a8 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -362,7 +362,7 @@ input CreateShowInput { description: HTMLString! credits: ShowCreditInput! genres: [Int!] - tags: [String] + tags: [String!] podcast_explicit: Boolean subtype: String! # TODO: not a string mixclouder: Boolean @@ -395,7 +395,7 @@ input CreateSeasonInput { show_id: Int! weeks: CreateSeasonWeeks! times: CreateSeasonTimes! - tags: [String] + tags: [String!] description: HTMLString subtype: String } From f45b6c45b98432a33f927864e6e13791500cd18e Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 13 Jul 2020 15:43:56 +0200 Subject: [PATCH 09/25] oopsies --- src/Classes/MyRadio/GraphQLUtils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Classes/MyRadio/GraphQLUtils.php b/src/Classes/MyRadio/GraphQLUtils.php index 8cd861f13..a55d1a40d 100644 --- a/src/Classes/MyRadio/GraphQLUtils.php +++ b/src/Classes/MyRadio/GraphQLUtils.php @@ -108,7 +108,7 @@ public static function isAuthorisedToAccess( $resolvedObject = null ) { $caller = MyRadio_Swagger2::getAPICaller(); - if ($caller === null) { + if (empty($caller)) { throw new MyRadioException('No valid authentication data provided', 401); } if ($caller->hasAuth(AUTH_APISUDO)) { From d3355a6c1f90df37e2776f36684f4028e70f7db6 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 20 Jul 2020 16:14:09 +0200 Subject: [PATCH 10/25] Expose genres and subtypes to GQL --- schema/api.graphql | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/schema/api.graphql b/schema/api.graphql index 8f24657a8..036e41357 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -241,6 +241,11 @@ type ShowCredit { User: User } +type Genre { + value: Int! + text: String! +} + type Show implements Node & MyRadioObject @auth(hook: ViewShow) { id: ID! itemId: Int! @bind(method: "getID") @@ -336,6 +341,8 @@ type Query { allShows(current_term_only: Boolean): [Show] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Show", method: "getAllShows") + allSubtypes: [ShowSubtype!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_ShowSubtype", method: "getAll") @auth(constants: []) + currentTimeslot: Timeslot @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Timeslot", method: "getCurrentTimeslot") nextTimeslot: Timeslot @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Timeslot", method: "getNextTimeslot") nineDaySchedule(weekno: Int!, year: Int): Timeslot @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Timeslot", method: "get9DaySchedule") @@ -346,6 +353,8 @@ type Query { allMailingLists: [MailingList!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_List", method: "getAllLists") mailingListByName(name: String!): MailingList @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_List", method: "getByName") + + allGenres: [Genre!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getGenres") @auth(constants: []) } type Mutation { From 349d99a91662f54ff98bbd4fde0a174ba1842aef Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 20 Jul 2020 16:18:47 +0200 Subject: [PATCH 11/25] Expose credit types --- schema/api.graphql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/schema/api.graphql b/schema/api.graphql index 036e41357..edd760f52 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -241,6 +241,11 @@ type ShowCredit { User: User } +type CreditType { + value: Int! + text: String! +} + type Genre { value: Int! text: String! @@ -355,6 +360,7 @@ type Query { mailingListByName(name: String!): MailingList @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_List", method: "getByName") allGenres: [Genre!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getGenres") @auth(constants: []) + allCreditTypes: [CreditType!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getCreditTypes") @auth(constants: []) } type Mutation { From 3b480d1cc52544efface5fcbb047694cee4458ba Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 20 Jul 2020 16:33:34 +0200 Subject: [PATCH 12/25] Fix a small fubar in MyRadio_ShowSubtype::getAll --- src/Classes/ServiceAPI/MyRadio_ShowSubtype.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Classes/ServiceAPI/MyRadio_ShowSubtype.php b/src/Classes/ServiceAPI/MyRadio_ShowSubtype.php index f6e94ec7e..70381355f 100644 --- a/src/Classes/ServiceAPI/MyRadio_ShowSubtype.php +++ b/src/Classes/ServiceAPI/MyRadio_ShowSubtype.php @@ -107,7 +107,7 @@ public static function getAll() $subtypes[] = new self($row); } - return CoreUtils::setToDataSource($subtypes); + return $subtypes; } /** From 02bc3d1f2ae08ac7b27a832744dc36638b6edd8a Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Tue, 21 Jul 2020 19:33:21 +0200 Subject: [PATCH 13/25] Expose member search --- schema/api.graphql | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/schema/api.graphql b/schema/api.graphql index edd760f52..b7b5af745 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -333,6 +333,14 @@ type EmailDestination { alias: Alias } +type MemberSearchResult { + memberid: Int! + fname: String! + sname: String! + eduroam: String + local_alias: String +} + type Query { node(id: ID): Node @@ -361,6 +369,8 @@ type Query { allGenres: [Genre!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getGenres") @auth(constants: []) allCreditTypes: [CreditType!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getCreditTypes") @auth(constants: []) + + findMemberByName(name: String!, limit: Int): [MemberSearchResult]! @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_User", method: "findByName") @auth(constants: ["AUTH_APPLYFORSHOW"]) # TODO not 100% sure this is the best constant } type Mutation { From 471f3fa8e641d2a413e03c975f4be3bee5881c3b Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Tue, 21 Jul 2020 19:58:03 +0200 Subject: [PATCH 14/25] Update api.graphql --- schema/api.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/api.graphql b/schema/api.graphql index b7b5af745..5f7c95d77 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -370,7 +370,7 @@ type Query { allGenres: [Genre!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getGenres") @auth(constants: []) allCreditTypes: [CreditType!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getCreditTypes") @auth(constants: []) - findMemberByName(name: String!, limit: Int): [MemberSearchResult]! @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_User", method: "findByName") @auth(constants: ["AUTH_APPLYFORSHOW"]) # TODO not 100% sure this is the best constant + findMemberByName(name: String!, limit: Int): [MemberSearchResult!]! @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_User", method: "findByName") @auth(constants: ["AUTH_APPLYFORSHOW"]) # TODO not 100% sure this is the best constant } type Mutation { From 5ade6f4a06c1928a75a86129c637168ece94d047 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Tue, 21 Jul 2020 20:46:23 +0200 Subject: [PATCH 15/25] Expose findShowByTitle --- schema/api.graphql | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/schema/api.graphql b/schema/api.graphql index 5f7c95d77..8602c6b06 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -341,6 +341,11 @@ type MemberSearchResult { local_alias: String } +type FindShowByTitleResult { # TODO: icky + show_id: Int! + title: String! +} + type Query { node(id: ID): Node @@ -370,7 +375,8 @@ type Query { allGenres: [Genre!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getGenres") @auth(constants: []) allCreditTypes: [CreditType!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getCreditTypes") @auth(constants: []) - findMemberByName(name: String!, limit: Int): [MemberSearchResult!]! @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_User", method: "findByName") @auth(constants: ["AUTH_APPLYFORSHOW"]) # TODO not 100% sure this is the best constant + findMemberByName(name: String!, limit: Int): [MemberSearchResult!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_User", method: "findByName") @auth(constants: ["AUTH_APPLYFORSHOW"]) # TODO not 100% sure this is the best constant + findShowByTitle(term: String!, limit: Int!): [FindShowByTitleResult!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "findShowByTitle") @auth(constants: ["AUTH_APPLYFORSHOW"]) # TODO ditto } type Mutation { From 0ba7adff4aeb911fc17553b87faa631dcea622ee Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 3 Aug 2020 19:28:12 +0200 Subject: [PATCH 16/25] Stuff can be overridden for seasons and timeslots --- schema/api.graphql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/schema/api.graphql b/schema/api.graphql index 8602c6b06..e410efc89 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -265,6 +265,9 @@ type Show implements Node & MyRadioObject @auth(hook: ViewShow) { type Season implements Node & MyRadioObject @auth(hook: ViewShow) { id: ID! itemId: Int! @bind(method: "getID") + title: String! @meta(key: "title") + description: HTMLString! @meta(key: "description") + credits: [ShowCredit] show: Show! subtype: ShowSubtype! seasonNumber: Int! @@ -286,6 +289,8 @@ type Message { type Timeslot implements Node & MyRadioObject @auth(hook: ViewShow) { id: ID! itemId: Int! @bind(method: "getID") + title: String! @meta(key: "title") + description: HTMLString! @meta(key: "description") season: Season! timeslotNumber: Int! startTime: DateTime! @@ -374,7 +379,7 @@ type Query { allGenres: [Genre!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getGenres") @auth(constants: []) allCreditTypes: [CreditType!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "getCreditTypes") @auth(constants: []) - + findMemberByName(name: String!, limit: Int): [MemberSearchResult!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_User", method: "findByName") @auth(constants: ["AUTH_APPLYFORSHOW"]) # TODO not 100% sure this is the best constant findShowByTitle(term: String!, limit: Int!): [FindShowByTitleResult!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "findShowByTitle") @auth(constants: ["AUTH_APPLYFORSHOW"]) # TODO ditto } From d2503e0ae5ad79938b329b56e040a161f7d227d3 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 3 Aug 2020 21:42:38 +0200 Subject: [PATCH 17/25] Expose isTerm --- schema/api.graphql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/schema/api.graphql b/schema/api.graphql index e410efc89..36a6f93da 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -370,6 +370,8 @@ type Query { nextTimeslot: Timeslot @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Timeslot", method: "getNextTimeslot") nineDaySchedule(weekno: Int!, year: Int): Timeslot @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Timeslot", method: "get9DaySchedule") + isTerm: Boolean @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "isTerm") @auth(constants: []) + user(itemid: Int!): User @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_User", method: "getInstance") allTrainingStatuses: [TrainingStatus!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_TrainingStatus", method: "getAll") From 2995437f70c0919a6a0b948cbd0d775cee1e54c3 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 3 Aug 2020 21:47:02 +0200 Subject: [PATCH 18/25] Expose getPreviousTimeslots --- schema/api.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/schema/api.graphql b/schema/api.graphql index 36a6f93da..d7e39724f 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -369,6 +369,7 @@ type Query { currentTimeslot: Timeslot @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Timeslot", method: "getCurrentTimeslot") nextTimeslot: Timeslot @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Timeslot", method: "getNextTimeslot") nineDaySchedule(weekno: Int!, year: Int): Timeslot @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Timeslot", method: "get9DaySchedule") + previousTimeslots(time: Int, n: Int, filter: [Int]): [Timeslot] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Timeslot", method: "getPreviousTimeslots") isTerm: Boolean @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "isTerm") @auth(constants: []) From ec32877ab40bc84571184255635fb141c09cb728 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 3 Aug 2020 21:50:24 +0200 Subject: [PATCH 19/25] Expose upload_state --- schema/api.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/schema/api.graphql b/schema/api.graphql index d7e39724f..731478c0f 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -298,6 +298,7 @@ type Timeslot implements Node & MyRadioObject @auth(hook: ViewShow) { duration: Duration! messages: [Message] webpage: String! + uploadState: String @meta(key: "upload_state") } type Quote implements Node { From 834f47fbf1cc59d20927b797ba0a5ae73658b1cb Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 3 Aug 2020 21:54:33 +0200 Subject: [PATCH 20/25] Expose Timeslot photo --- schema/api.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/schema/api.graphql b/schema/api.graphql index 731478c0f..e0b33226c 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -296,6 +296,7 @@ type Timeslot implements Node & MyRadioObject @auth(hook: ViewShow) { startTime: DateTime! endTime: DateTime! duration: Duration! + photo: String messages: [Message] webpage: String! uploadState: String @meta(key: "upload_state") From fe49a574db58c9f530672e08c0b33a2778ad9fed Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 10 Aug 2020 18:46:46 +0200 Subject: [PATCH 21/25] Merge master --- schema/api.graphql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/schema/api.graphql b/schema/api.graphql index e3c35a349..3adb52a5c 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -19,7 +19,8 @@ directive @bind( enum CallingConvention { FirstArgCurrentUser, - FirstArgInput + FirstArgInput, + FirstArgCurrentObject } enum AuthHook { From f60f4dd0b4452a8ae736c83922cc95ff84a43723 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 10 Aug 2020 18:48:26 +0200 Subject: [PATCH 22/25] Expose includeMemberships --- schema/api.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/api.graphql b/schema/api.graphql index 3adb52a5c..0caa91120 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -110,7 +110,7 @@ type User implements Node & MyRadioObject { # Public information shows(current_term_only: Boolean): [Show!] @auth(constants: []) - officerships: [MemberOfficership!] @auth(constants: []) + officerships(includeMemberships: Boolean): [MemberOfficership!] @auth(constants: []) timeline: [UserTimelineEntry!] @auth(constants: []) From 2d7dbf66c26b86bc78c65b29976b29cdc75da637 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Mon, 10 Aug 2020 18:56:07 +0200 Subject: [PATCH 23/25] Move warnings into extensions of result --- src/Controllers/api/graphql.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Controllers/api/graphql.php b/src/Controllers/api/graphql.php index 2781b0b40..1c04df95c 100644 --- a/src/Controllers/api/graphql.php +++ b/src/Controllers/api/graphql.php @@ -370,7 +370,12 @@ function graphQlResolver($source, $args, GraphQLContext $context, ResolveInfo $i $warnings = $ctx->getWarnings(); if (count($warnings) > 0) { - $result['warnings'] = $warnings; + $result['extensions'] = array_merge( + $result['extensions'] ?? [], + [ + 'warnings' => $warnings + ] + ); } $corsWhitelistOrigins = [ From 25a45efa5f446f081e14243668994eebf794edfa Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sun, 16 Aug 2020 18:20:05 +0200 Subject: [PATCH 24/25] Update some outdated comments and errors --- src/Controllers/api/graphql.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Controllers/api/graphql.php b/src/Controllers/api/graphql.php index ac7b1e20c..96573dded 100644 --- a/src/Controllers/api/graphql.php +++ b/src/Controllers/api/graphql.php @@ -126,11 +126,11 @@ function graphQlResolver($source, $args, GraphQLContext $context, ResolveInfo $i $fieldName = $info->fieldName; // Query and Mutation deserve special handling if ($typeName === 'Query' || $typeName === 'Mutation') { - // If we're on the Query type, we're entering the graph, so we'll want a static method. - // Unlike elsewhere in the graph, we can assume everything on Query will have an @bind. + // If we're on the Query or Mutation type, we're entering the graph, so we'll want a static method. + // Unlike elsewhere in the graph, we can assume everything on Query/Mutation will have an @bind. $bindDirective = GraphQLUtils::getDirectiveByName($info, 'bind'); if (!$bindDirective) { - throw new MyRadioException("Tried to resolve $fieldName on Query but it didn't have an @bind"); + throw new MyRadioException("Tried to resolve $fieldName on $typeName but it didn't have an @bind"); } $bindArgs = GraphQLUtils::getDirectiveArguments($bindDirective); if (isset($bindArgs['class'])) { @@ -139,14 +139,14 @@ function graphQlResolver($source, $args, GraphQLContext $context, ResolveInfo $i $className = $bindArgs['class']->value; } else { throw new MyRadioException( - "Tried to resolve $fieldName on Query but its @bind didn't have a class" + "Tried to resolve $fieldName on $typeName but its @bind didn't have a class" ); } if (isset($bindArgs['method'])) { $methodName = $bindArgs['method']->value; } else { throw new MyRadioException( - "Tried to resolve $fieldName on Query but its @bind didn't have a method" + "Tried to resolve $fieldName on $typeName but its @bind didn't have a method" ); } // Wonderful! From 67cf048736000288ae2c9a347d4c7c3c3f138378 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sat, 5 Dec 2020 19:50:27 +0000 Subject: [PATCH 25/25] Disable createShow and add sendMessageToTimeslot --- schema/api.graphql | 105 +++++++++++--------- src/Classes/ServiceAPI/MyRadio_Timeslot.php | 12 +++ src/Controllers/api/graphql.php | 3 + 3 files changed, 71 insertions(+), 49 deletions(-) diff --git a/schema/api.graphql b/schema/api.graphql index ddfbd342b..ccd2ed9f5 100644 --- a/schema/api.graphql +++ b/schema/api.graphql @@ -20,7 +20,8 @@ directive @bind( enum CallingConvention { FirstArgCurrentUser, FirstArgInput, - FirstArgCurrentObject + FirstArgCurrentObject, + InputAsArgs } enum AuthHook { @@ -463,56 +464,62 @@ type Query { findShowByTitle(term: String!, limit: Int!): [FindShowByTitleResult!] @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Scheduler", method: "findShowByTitle") @auth(constants: ["AUTH_APPLYFORSHOW"]) # TODO ditto } -type Mutation { - createShow(input: CreateShowInput): Show @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Show", method: "create", callingConvention: FirstArgInput) -} - -input ShowCreditInput { # this is icky - memberid: [Int!]! - credittype: [Int!]! +#input ShowCreditInput { # this is icky +# memberid: [Int!]! +# credittype: [Int!]! +#} +# +#input CreateShowInput { +# title: String! +# description: HTMLString! +# credits: ShowCreditInput! +# genres: [Int!] +# tags: [String!] +# podcast_explicit: Boolean +# subtype: String! # TODO: not a string +# mixclouder: Boolean +# "Unused" +# location: Int +#} +# +#input CreateSeasonWeeks { +# wk1: Boolean! +# wk2: Boolean! +# wk3: Boolean! +# wk4: Boolean! +# wk5: Boolean! +# wk6: Boolean! +# wk7: Boolean! +# wk8: Boolean! +# wk9: Boolean! +# wk10: Boolean! +#} +# +#input CreateSeasonTimes { +# day: [Int!]! +# "Seconds since midnight" +# stime: [Int!]! +# "Seconds since midnight" +# etime: [Int!]! +#} +# +#input CreateSeasonInput { +# show_id: Int! +# weeks: CreateSeasonWeeks! +# times: CreateSeasonTimes! +# tags: [String!] +# description: HTMLString +# subtype: String +#} + +input SendMessageInput { + timeslotId: Int! + message: String! } -input CreateShowInput { - title: String! - description: HTMLString! - credits: ShowCreditInput! - genres: [Int!] - tags: [String!] - podcast_explicit: Boolean - subtype: String! # TODO: not a string - mixclouder: Boolean - "Unused" - location: Int -} - -input CreateSeasonWeeks { - wk1: Boolean! - wk2: Boolean! - wk3: Boolean! - wk4: Boolean! - wk5: Boolean! - wk6: Boolean! - wk7: Boolean! - wk8: Boolean! - wk9: Boolean! - wk10: Boolean! -} - -input CreateSeasonTimes { - day: [Int!]! - "Seconds since midnight" - stime: [Int!]! - "Seconds since midnight" - etime: [Int!]! -} - -input CreateSeasonInput { - show_id: Int! - weeks: CreateSeasonWeeks! - times: CreateSeasonTimes! - tags: [String!] - description: HTMLString - subtype: String +type Mutation { +# createShow(input: CreateShowInput): Show @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Show", method: "create", callingConvention: FirstArgInput) + sendMessageToTimeslot(input: SendMessageInput): Show @bind(class: "\\MyRadio\\ServiceAPI\\MyRadio_Timeslot", method: "sendMessageToTimeslot", callingConvention: InputAsArgs) } schema { diff --git a/src/Classes/ServiceAPI/MyRadio_Timeslot.php b/src/Classes/ServiceAPI/MyRadio_Timeslot.php index ba507e0e8..3891d3525 100644 --- a/src/Classes/ServiceAPI/MyRadio_Timeslot.php +++ b/src/Classes/ServiceAPI/MyRadio_Timeslot.php @@ -1238,6 +1238,18 @@ public function sendMessage($message) return $this; } + /** + * Ditto, but a helper for GraphQL + * @param int $timeslotid + * @param string $message + */ + public static function sendMessageToTimeslot($timeslotid, $message) + { + /** @var self $timeslot */ + $timeslot = self::getInstance($timeslotid); + $timeslot->sendMessage($message); + } + /** * Signs the given user into the timeslot to say they were * on air at this time, if they haven't been signed in already. diff --git a/src/Controllers/api/graphql.php b/src/Controllers/api/graphql.php index 00c99bd1a..01a530689 100644 --- a/src/Controllers/api/graphql.php +++ b/src/Controllers/api/graphql.php @@ -166,6 +166,9 @@ function graphQlResolver($source, $args, GraphQLContext $context, ResolveInfo $i case 'FirstArgInput': $val = $meth->invokeArgs(null, [$args['input']]); break; + case 'InputAsArgs': + $val = GraphQLUtils::invokeNamed($meth, null, $args['input']); + break; default: throw new MyRadioException("Unknown calling convention $cc"); }