diff --git a/subjects/graphql/README.md b/subjects/graphql/README.md new file mode 100644 index 00000000..bc8962c0 --- /dev/null +++ b/subjects/graphql/README.md @@ -0,0 +1,126 @@ +## GraphQL + +### Objectives + +The objective of this project is to learn graphql query language by creating your own profile. It will be provided,\ +by the platform, a graphql endpoint that is connected to the database. So you can query this endpoint to obtain the information you desire.\ +**Note** that for security reasons some tables are private and some are public, you will only be provided with certain content. + +Your profile must have at least: + +- Basic user identification +- XP amount +- level +- grades +- skills +- statistics + +### Instructions + +> Here is a small introduction and advantages of graphQL, if you want to read more about graphQL you can visit there [site](https://graphql.org/) + +GraphQL is a query language for any API endpoint and runtime. The syntax lets you specify what data you want to receive from that API.\ +You must have in mind that this language is used for API endpoints and not database. Unlike SQL,\ +graphql query does not go to your database directly but to your graphql API endpoint which can connect to a database or any other data source. + +You are already familiar to REST, since REST is a robust methodology of creating APIs and not elastic/scalable.\ +It can be at the same time painful, because it requires individual creation of each API, example: `v1/user/item/{id}`, `v1/post/item/{id}` and so on. + +The main feature of graphql compared to REST is that it lets you ask for specific information. And even better then that is the nesting feature. + +Lets take for instance the social network project, if the server had a Graphql API that was connected to the database we could query this API using graphql language: + +```js +query { + user { + name + } +} +``` + +This simple query will return an array with the name of the users. Imagine if you wanted the `date_of_birth`,\ +you could just add this attribute to the query, example: + +```js +query { + user { + name + dateOfBirth + } +} +``` + +Example for the nesting fetching: + +```js +query { + user(id: "13") { + name + dateOfBirth + followers { + id + name + } + } +} +``` + +For this example we ask for the users followers and request their names and ids.\ +The examples above are simple examples, **note** that this query is require the introduction of variables(arguments)\ + so it will return just one user, the user that as the id equal to `13`. + +In graphql the usage of arguments can be specified in the schema of the API endpoint. Here is the *docs* for the graphql endpoint you are going to use, [Schema](https://01.alem.school/public/subjects/grapqhl) + + + + + +--- + +### Usage + +> Here are the list of tables that you are available to query: + +- **User table**: + +| id | githubLogin | +| --- | ----------: | +| 1 | person1 | +| 2 | person2 | +| 3 | person3 | + +- **Transactions table**: + +| id | type | amount | userId | createdAt | +| --- | :--: | -----: | -----: | -------------------------------: | +| 1 | xp | 234 | 1 | 2019-03-14T12:02:23.168726+00:00 | +| 2 | xp | 1700 | 2 | 2019-03-14T12:02:23.168726+00:00 | +| 3 | xp | 175 | 3 | 2019-03-14T12:02:23.168726+00:00 | + +- **Progress table**: + +| id | userId | attrs | bestResultId | objectId | +| --- | :----: | -----------------------: | -----------: | -------: | +| 1 | 1 | `{"name": "memory", ...` | s | 198 | +| 2 | 2 | `{"name": "memory", ...` | s | 198 | +| 3 | 3 | `{"name": "memory", ...` | s | 198 | + +- **Results table**: + +| id | grade | progressId | +| --- | ----: | ---------: | +| 1 | 0 | 58 | +| 2 | 0 | 58 | +| 3 | 1 | 58 | + +- **Object table**: + +| id | grade | progressId | +| --- | ----: | ---------: | +| 1 | 0 | 58 | +| 2 | 0 | 58 | +| 3 | 1 | 58 | + +This project will help you learn about: + +- [Graphql](https://graphql.org/) language diff --git a/subjects/graphql/audit/README.md b/subjects/graphql/audit/README.md new file mode 100644 index 00000000..e69de29b diff --git a/subjects/graphql/index.html b/subjects/graphql/index.html new file mode 100644 index 00000000..abdbc553 --- /dev/null +++ b/subjects/graphql/index.html @@ -0,0 +1,119 @@ + + + + Graphql Doc + + + + + + +
+

Schema Documentation

+
+ +

A Graphql schema provides a root type for each kind of operation

+
+

ROOT TYPES

+
+
+
+
+ + diff --git a/subjects/graphql/lib/schema.js b/subjects/graphql/lib/schema.js new file mode 100644 index 00000000..34ea5408 --- /dev/null +++ b/subjects/graphql/lib/schema.js @@ -0,0 +1,157 @@ +const backImg = + 'https://cdn1.iconfinder.com/data/icons/thin-ui-1/100/Noun_Project_100Icon_1px_grid_thin_ic_arrow_left_simple-512.png' +const body = document.body +const main = document.querySelector('.doc-explore-content') +const docDisplay = document.querySelector('.display-doc') +const exist = () => document.querySelector('.back') +const removeComments = (obj) => + Object.fromEntries(obj.filter((v) => v[0] !== 'comment')) + +const getData = async (pathToFile) => { + const data = await fetch(pathToFile) + .then((data) => data.json()) + .catch(console.log) + return data +} + +const generateLinks = (k, v) => { + const h4 = document.createElement('h4') + const a = document.createElement('a') + a.innerText = v + h4.innerHTML = k + h4.appendChild(a) + docDisplay.appendChild(h4) + return a +} + +const generateAllQueries = (data) => { + const docTitle = document.querySelector('.doc-title') + docDisplay.innerHTML = '' + const back = backArrow() + docTitle.appendChild(back) + for (let obj of data.types) { + for (const [k, _] of Object.entries(removeComments(Object.entries(obj)))) { + generateContent(k, obj) + } + } +} + +const loadRootTypes = async () => { + const data = await getData('./lib/schema.json') + for (let v of data.root_types) { + const h4 = document.createElement('h4') + const a = document.createElement('a') + a.addEventListener('click', (event) => changeContent(event, data)) + + for (const [k, value] of Object.entries(v)) { + a.innerText = value + h4.innerHTML = `${k}: ` + h4.appendChild(a) + docDisplay.appendChild(h4) + } + } + const a = generateLinks('all: ', 'all_available_queries') + a.addEventListener('click', () => generateAllQueries(data)) +} + +const generateContent = (t, data) => { + docDisplay.innerHTML += `type ${t.toUpperCase()} {` + for (const [k, v] of Object.entries(data[t])) { + generateLinks(` ${k}: `, v) + } + docDisplay.innerHTML += `}

` + for (let v of document.getElementsByTagName('a')) { + v.addEventListener('click', (e) => changeContent(e, data)) + } +} +const backArrow = () => { + const title = document.querySelector('.title') + const typeDescription = document.querySelector('.comment') + const contType = document.querySelector('.doc-type') + const origType = contType.innerText + const origTitle = title.innerText + const origTypeDescription = typeDescription.innerText + const back = document.createElement('img') + back.className = 'back' + back.src = backImg + + back.addEventListener('click', () => { + back.remove() + docDisplay.innerHTML = '' + title.innerText = origTitle + contType.innerText = origType + typeDescription.innerText = origTypeDescription + loadRootTypes() + }) + return back +} + +const allArgs = (data) => { + for (const [k, v] of Object.entries(data.arguments)) { + docDisplay.innerHTML += `

${k}: (${v.description}) // ${v.comment}

` + } +} + +const changeContent = ({ target }, data) => { + const title = document.querySelector('.title') + const typeDescription = document.querySelector('.comment') + const contType = document.querySelector('.doc-type') + const docTitle = document.querySelector('.doc-title') + + const t = target.innerText + const back = backArrow() + + title.innerText = t.toUpperCase() + contType.innerText = `${t.toUpperCase()} fields` + docDisplay.innerHTML = '' + if (/\[\w*\!\]\!/g.test(t)) { + const b = t.replace(/\[|\]|\!/g, '') + const obj = data.types.filter((v) => v[b])[0] + typeDescription.innerText = obj.comment + generateContent(b, obj) + } else if (t === 'arguments') { + allArgs(data) + !exist() && docTitle.appendChild(back) + } else { + generateContent(t, data) + !exist() && docTitle.appendChild(back) + } +} + +loadRootTypes() + +/* search */ +const updateResult = (dataToSearch, { target }) => { + docDisplay.innerHTML = '' + + dataToSearch.search.map((type) => + target.value.split(' ').map((w) => { + if (type.toLowerCase().indexOf(w.toLowerCase()) !== -1) { + docDisplay.innerHTML += `
  • ${type}
  • ` + document + .querySelectorAll('.query-result') + .forEach((ele) => + ele.addEventListener('click', (e) => changeContent(e, dataToSearch)) + ) + } + }) + ) +} + +const initSearch = async () => { + const data = await getData('./lib/schema.json') + const input = document.querySelector('.search') + + input.addEventListener('focus', () => { + const bg = document.querySelector('.back') + if (!bg) { + const back = backArrow() + document.querySelector('.doc-title').appendChild(back) + } + docDisplay.innerHTML = '' + }) + input.oninput = (e) => updateResult(data, e) +} + +initSearch() +/*--------------*/ diff --git a/subjects/graphql/lib/schema.json b/subjects/graphql/lib/schema.json new file mode 100644 index 00000000..8b749501 --- /dev/null +++ b/subjects/graphql/lib/schema.json @@ -0,0 +1,101 @@ +{ + "search": [ + "[user!]!", + "[object!]!", + "[transaction!]!", + "[progress!]!", + "[result!]!", + "query", + "arguments" + ], + "root_types": [{ "query": "query" }, { "arguments": "arguments" }], + "query": { + "user": "[user!]!", + "object": "[object!]!", + "transaction": "[transaction!]!", + "progress": "[progress!]!", + "result": "[result!]!" + }, + "types": [ + { + "user": { + "id": "ID!", + "attrs": "String!", + "createdAt": "String!", + "discordDMChannelId": "String!", + "discordId": "String!", + "discordLogin": "String!", + "githubId": "String!", + "githubLogin": "String!", + "profile": "String!", + "updatedAt": "String!" + }, + "comment": "Fetch data from the table: \"user\"" + }, + { + "object": { + "id": "ID!", + "attrs": "String!", + "authorId": "String!", + "childrenAttrs": "String!", + "createdAt": "String!", + "externalRelationUrl": "String!", + "name": "String!", + "type": "String!", + "status": "String!", + "updatedAt": "String!" + }, + "comment": "Fetch data from the table: \"object\"" + }, + { + "transaction": { + "id": "ID!", + "attrs": "String!", + "amount": "Int!", + "createdAt": "String!", + "type": "String!", + "userId": "Int!" + }, + "comment": "Fetch data from the table: \"transaction\"" + }, + { + "progress": { + "id": "ID!", + "attrs": "String!", + "bestResultId": "Int!", + "createdAt": "String!", + "eventId": "Int!", + "groupId": "Int!", + "objectId": "Int!", + "updatedAt": "String!", + "userId": "Int!" + }, + "comment": "Fetch data from the table: \"progress\"" + }, + { + "result": { + "id": "ID!", + "progressId": "Int!", + "attrs": "String!", + "createdAt": "String!", + "grade": "Int!", + "updatedAt": "String!" + }, + "comment": "Fetch data from the table: \"result\"" + } + ], + "arguments": { + "where": { + "description": "bool_exp", + "comment": "filter the rows returned" + }, + "order_by": { + "description": "[order_by!]", + "comment": "sort the rows by one or more columns" + }, + "limit": { + "description": "Int", + "comment": "limit the number of rows returned" + } + } +}