serlo / api.serlo.org Goto Github PK
View Code? Open in Web Editor NEWPublic GraphQL API of https://serlo.org/
Home Page: https://api.serlo.org/___graphql
License: Apache License 2.0
Public GraphQL API of https://serlo.org/
Home Page: https://api.serlo.org/___graphql
License: Apache License 2.0
Pretty much all types that return a collection should be refactored to return a Relay Connection so that we can handle pagination in the future without breaking stuff. Furthermore, we might want to review GitHub's GraphQL API since they seem to extend the idea of Relay Connections:
{
securityVulnerabilities(first: 5) {
totalCount
edges {
__typename
cursor
node {
advisory {
id
}
}
}
nodes {
advisory {
id
}
}
}
}
Basically, every collection in the GraphQL API returns the following:
More specifically, we still use legacy collections in these locations:
The option bypassCache
in SerloDataSource
is not needed anymore.
Currently, we add the discriminator each time in set*
in SerloDataSource
. Instead, we should just add them to the server response instead so that we can get rid of the custom behavior.
{
uuid(id: 57201) {
__typename
... on GroupedExercise {
currentRevision {
id
content
}
}
}
}
still returns the previous revision, although a new revision was checked out a couple of days ago.
Currently, we can't differentiate Sentry issues by env. We should add that somehow (e.g. via env variable or something).
Version should be without api.serlo.org prefix.
E.g.
{
uuid(id:1855) {
...on Article {
taxonomyTerms {
navigation {
data
}
}
}
}
}
Idea: services communicate which cache keys they can have so that we can use the information to fill the cache beforehand.
Basically, the GraphQL types should look like this:
extend type Query {
_getCacheKeys(
after: String
before: String
first: Int
last: Int
): Query_GetCacheResult!
}
type Query_GetCacheResult {
edges: [CacheKeyCursor!]!
nodes: [String!]!
totalCount: Int!
pageInfo: PageInfo!
}
type CacheKeyCursor {
cursor: String!
node: String!
}
See src/graphql/schema/notification
for an example on how to work with connections.
Furthermore, we need a legacy API endpoint that returns the cache keys for serlo.org:
/api/cache-keys
returns an array of strings (i.e. the cache keys). This request won't be instance-aware, so it should return cache keys from all instances._getCacheKeys
to src/graphql/schema/cache
._getCacheKeys
that uses /api/cache-keys
.See serlo/serlo.org-legacy#474. Zend sometimes serializes arrays as objects (i.e. Record<number, unknown>
). We should be able to handle those in payloads everywhere (we already handle this somewhere for notifications) since we apparently can't configure Zend to serialize stuff correctly w/o writing our own serializer...
{
uuid(alias:{ instance:de, path: "/mathe/zahlen-größen/prozent--zinsrechnung/prozent"}) {
id
__typename
... on Article {
taxonomyTerms {
id
navigation {
data
}
}
}
}
}
doesn't work in staging (vs. production)
Currently, all GraphQL mutations failures are grouped together. We should at least add the operation name to Sentry so that we can differentiate different GraphQL mutation failures.
{
"message": "403: Forbidden",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"uuid"
],
"extensions": {
"code": "FORBIDDEN",
"response": {
"url": "https://de.serlo.org/api/uuid/1855",
"status": 403,
"statusText": "Forbidden",
"body": {
"reason": "Invalid authorization header"
}
},
"exception": {
"stacktrace": [
"ForbiddenError: 403: Forbidden",
" at SerloDataSource.<anonymous> (/Users/jk/Projects/@serlo/api.serlo.org/node_modules/apollo-datasource-rest/src/RESTDataSource.ts:135:15)",
" at Generator.next (<anonymous>)",
" at /Users/jk/Projects/@serlo/api.serlo.org/node_modules/apollo-datasource-rest/dist/RESTDataSource.js:8:71",
" at new Promise (<anonymous>)",
" at __awaiter (/Users/jk/Projects/@serlo/api.serlo.org/node_modules/apollo-datasource-rest/dist/RESTDataSource.js:4:12)",
" at SerloDataSource.errorFromResponse (/Users/jk/Projects/@serlo/api.serlo.org/node_modules/apollo-datasource-rest/dist/RESTDataSource.js:75:16)",
" at SerloDataSource.<anonymous> (/Users/jk/Projects/@serlo/api.serlo.org/node_modules/apollo-datasource-rest/src/RESTDataSource.ts:102:24)",
" at Generator.next (<anonymous>)",
" at /Users/jk/Projects/@serlo/api.serlo.org/node_modules/apollo-datasource-rest/dist/RESTDataSource.js:8:71",
" at new Promise (<anonymous>)"
]
}
}
}
],
"data": {
"uuid": null
}
}
As a consumer of the API (e.g. frontend), it would be helpful to have access to the TypeScript types (that we mostly already have). Need to make sure, that:
@serlo/api
with same version number as the docker image.See #14.
Probably create an interface for that (AbstractExercise
) that will be implemented by GroupedExercise
& Exercise
.
Similarly to https://github.com/serlo/api.serlo.org/blob/master/src/graphql/schema/notification/resolvers.ts#L77, we need a mutation
mutation setUuidState(id: Int!, trashed: Boolean!): AbstractUuid
that sets the trashed
property of the corresponding uuid.
setUuidState
(probably to https://github.com/serlo/api.serlo.org/blob/master/src/graphql/schema/uuid/abstract-uuid/resolvers.ts).setUuidState
.setUuidState
(see https://github.com/serlo/api.serlo.org/blob/master/__tests-pacts__/serlo.org/set-notification-state.ts).We want to add threads to the uuid type. So no additional root query necessary. Something like:
{
uuid(id: 1855) {
threads {
totalCount
nodes {
id
createdAt
updatedAt
title
archived
trashed
object # Back link to the uuid the thread is tied to
comments {
totalCount
nodes {
id
content
createdAt
updatedAt
author
}
}
}
}
}
}
Furthermore, we need mutations for various types of actions:
mutation createThread(
# ...
}
# createComment
# archiveThread
# ...
/api/threads/:object-id
returns all the threads for the object with the given id (with thread ids only)/api/thread/:id
returns the thread with the given id (including its comments)/api/create-thread
creates a threaduuid
should be able to resolve /:id
to the specific uuid./user/profile/:username
.uuid
should be able to resolve /user/profile/:id
to the specific user.uuid
should be abe to resolve /user/profile/:username
to the specific user (might need a mapping from username to id).Second part of #41.
We need a mutation
mutation _updateCache(keys: [String!]!)
that tells the API to update the cache of the given keys. To achieve this, we need to map the cache key to the corresponding data source (i.e. *.serlo.org*
maps to SerloDataSource
, spreadsheet-*
maps to GoogleSheetApi
) and then tells it to update the cache (i.e. executing the corresponding get
method without returning early when the cache is already set).
_updateCache
.*.serlo.org*
cacheKey.spreadsheet-*
cacheKey.Approach for extending the API:
Things to consider:
/api
(e.g. /api/license/<id>
. Prefer GET requests where possible (i.e. prefer URL parameters over POST body)Next Priorities:
Status:
We should rewrite the queries in tests/schema/uuid/abstract-navigation-child.ts using GraphQL variables so that autoformatting & IntelliSense works for them, too.
Example query using GraphQL variables:
api.serlo.org/__tests__/schema/uuid/user.ts
Lines 54 to 68 in e66c387
Currently, https://api.serlo.org/___graphql
goes through the Cloudflare Worker anyways, so there is no need for a specific playground service token. We could remove that and let the local playground use the (mocked) Cloudflare Worker secret instead.
Similarly to AbstractTaxonomyTermChild
, a common AbstractNavigationChild
interface would make our current tests easier.
See comments in #25.
Would be nice if we can add some documentation to the GraphQL schema since the nomenclature in serlo.org is not always obvious :)
Changes to the navigation aren't reflected in the cache, yet.
See comments in #35.
See #14.
We allow special characters to be part of the url. We should probably url-encode these at the API level.
For each type, we have a _set*
mutation that allows serlo.org to set the cache. This is kind of annoying to maintain. Moreover, the GraphQL typing doesn't help us in this place, anyways. So we should just get rid of it and combine a setCache
mutation that acceps the key and the value.
steps:
{
uuid(id: 18981) {
__typename
id
... on User {
trashed
username
description
}
}
}
results in
{
"data": {
"uuid": {
"__typename": "User",
"id": 18981,
"trashed": false,
"username": "wolfgang",
"description": "NULL"
}
}
}
Basically: move the logic from https://github.com/serlo/frontend/blob/9f0fcd74be4cedf0bd07d213c23f28e4f79071d1/src/events/event.ts into the API, resolving ids to their corresponding data structure.
Maybe: add worker that we can run nightly that sets / updates the cache for every (relevant) entity?
POST requests (e.g. /api/set-notification-state
) should also have a contract test.
IMHO, the documentation directly offered by GraphQL wasn't really helpful (see #7 and its implementation):
Instead, we should add a human-created documentation that can also explain high-level stuff, e.g.:
docs.api.serlo.org
serlo.dev
/ docs.serlo.org
that we can use in the future for other projects (currently it would be nice for content API & API). Might also be a nice place for changelogs etc.Needs some thinking before implementing. Basically we have two use cases:
uuid
, we want to be able to access the taxonomy path to that entity/page for the breadcrumbsuuid
s over uuid
Probably implement that similar to currentRevision:
uuid
uuid
and requesting taxonomyParent
(name tbd), do sub-requests to uuid
if needed. Something like{
uuid(id: 1855) {
... on Article {
taxonomyPath: { # List of taxonomy elements, probably has sub types
id
title
}
}
}
}
At the moment, the data sources classes share only the super class RestDataSource without any super class and/or interface that is app specific. When the data sources scale we may have maintainability problems. Besides that, it can lead to verbose code when we have to use such classes in the same logic (v.g. resolver for updateCache, see #57 ).
CacheableDataSource would be a good name for super class/interface here.
There are some unresolved / wrong peerDependencies. Although those doesn't seem to cause errors in the running, we should fix them were possible.
We want to expose the notifications of the currently authenticated user via our API. Since every notification has an event associated with it, this basically needs two components:
In the end, we should have a notifications query of the following form:
{
notifications() { # We might add filters at a later point
totalCount # The total number of notifications. Probably fixed at around ~20 right now in the legacy system
nodes { # The actual list of notifications
id # ID of the notifcation. We probably want to expose that as a string to be compatible with what we want to achieve in the future
unread # True iff the notification has not been read by the user.
event { # The event that triggered the notification.
id # ID of the event. Again, a string.
type # Type of the event. See a list of available types below.
instance # The instance the event has been triggered in.
date # Datetime the event has been created.
actor { # The user that triggered the event.
# User subtype
}
object { # The object that the event has been triggered on.
# Uuid subtype
}
payload # Arbitrary JSON-String containing additional information about the event. We should document that somewhere (per event type).
}
}
}
}
Furthermore, there should be a way to mark notifications as unread/read.
mutation setNotificationState(
id # The ID of the notification which read status should be changed.
unread # The value the unread state should be set to.
)
Note: Both query and mutation are tied to the currently authenticated user. So they need to work with the user token.
These are all the event types that we currently support in the legacy system. We might want to rename that on the API level so that we have a consistent naming scheme in the future.
discussion/comment/archive
discussion/comment/create
discussion/create
discussion/restore
entity/create
entity/link/create
entity/link/remove
entity/revision/add
entity/revision/checkout
entity/revision/reject
license/object/set
taxonomy/term/associate
taxonomy/term/create
taxonomy/term/dissociate
taxonomy/term/parent/change
taxonomy/term/update
uuid/restore
uuid/trash
/api/notifcations/:user-id
returns all the notifications for the user with the given id (with event id only)/api/event/:id
returns the event with the given id/api/set-notification-state/:id
sets the unread state for the notification of the given idObserving a couple of 413 that is probably the API server. We should increase the maximum request body size (at least for services like serlo.org since the legacy API requests may be quite large).
To check which entities the current user is subscribed to. An example query would be:
{
subscriptions(first: 5) { # Returns a connection of Uuids
totalCount
nodes {
__typename
id
}
}
}
Basic approach:
subscriptions
query in src/graphql/schema/subscriptions
(user-specific, so similarly to src/graphql/schema/notifications
)subscriptions
that mocks the legacy endpoint GET /api/subscriptions/:user-id
(that should return a list of ids)subscriptions
that uses the legacy endpoint GET /api/subscriptions/:user-id
(similarly to _cacheKeys
)Third part of #41.
Basically:
_cacheKeys
and _updateCache
to fill/update the whole cache:
src/worker.ts
for that (independent of the current server) for now. Might refactor the repo into a monorepo. Will see.graphql-request
to do the requests. Probably something like:
_cacheKeys
, update those keys using _updateCache
, continue until no cache keys left.src/worker.ts
(using the provided environment variables for arguments). As noted above: we might want to refactor this repo into a monorepo for that since the worker will have different dependencies compared to the server.
See serlo/frontend#420 (comment). Might be the case for other types, too. Should also check the behavior of sub resolvers (and potentially change the type if we need to cover null
).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.