Skip to content

Latest commit

 

History

History
528 lines (462 loc) · 21.4 KB

gql_ws_zh_jan13.md

File metadata and controls

528 lines (462 loc) · 21.4 KB

Szerveroldali webprogramozás - GraphQL, Websocket pótzárthelyi

2022. január 13. 16:00-19:15 (3 óra kidolgozás + 15 perc beadás)

Tartalom:

Tudnivalók

  • Kommunikáció
    • A Teams csoport Általános csatornáján a zárthelyi egész ideje alatt lesz egy meeting! Erősen ajánlott, hogy ehhez csatlakozzatok, hiszen elsősorban ebben a meetingben válaszolunk a felmerülő kérdésekre, valamint az esetleges időközben felmerülő információkat is itt osztjuk meg veletek!
    • Ha a zárthelyi közben valamilyen problémád, kérdésed adódik, akkor keresd az oktatókat a meetingben vagy privát üzenetben (Teams chaten).
  • Időkeret
    • A zárthelyi megoldására 3 óra áll rendelkezésre: 16:00-19:00.
    • Oszd be az idődet! Ha egy feladat nem megy, akkor inkább ugord át (legfeljebb később visszatérsz rá), és foglalkozz a többivel, hogy ne veszíts pontot olyan feladatból, amit meg tudnál csinálni!
  • Beadás
    • A beadásra további 15 perc áll rendelkezésre: 19:00-19:15. Ez a +15 perc ténylegesen a beadásra van! 19:15 után a Canvas lezár, és további beadásra nincs lehetőség!
    • Ha előbb végzel, természetesen 19:15-ig bármikor beadhatod a feladatot.
    • A feladatokat node_modules mappa nélkül kell becsomagolni egy .zip fájlba, amit a Canvas rendszerbe kell feltölteni!
    • A dolgozat megfelelő és hiánytalan beadása a hallgató felelőssége. Mivel a dolgozat végén külön 15 perces időkeretet adunk a feladat megfelelő, nyugodt körülmények közötti beadására, ebből kifolyólag ilyen ügyekben nem tudunk utólagos reklamációknak helyt adni. Tehát ha valaki a zárthelyi után jelzi, hogy egy vagy több fájlt nem adott be, akkor azt sajnos nem tudjuk elfogadni.
  • Értékelés
    • A legutoljára beadott megoldás lesz értékelve.
    • A zárthelyin legalább a pontok 40%-át, vagyis legalább 12 pontot kell elérni, ez alatt a zárthelyi sikertelen.
    • A GraphQL-re vagy Websocket-re nincs külön-külön minimumpont, összesen kell elérni legalább a 12 pontot, ami akár úgy is összetevődhet, hogy a GraphQL feladat 12 pontos, a Websocket pedig 0 pontos.
    • Vannak részpontok.
    • A pótzárthelyin nem lehet rontani a zárthelyi eredményéhez képest, csak javítani. Ez azt jelenti, ha valaki egy adott témakörből (pl. REST API) megírja mindkét zárthelyit (a normált és a pótot is), akkor a jegyébe a kettő közül a jobbik eredményt fogjuk beszámítani. Azonban fontos, hogy ez a "jobbik eredmény" legalább 40% (vagyis legalább 12 pont) legyen, különben az illető nem teljesítette a tárgyat, hiszen az adott témakörből nem érte el a minimális 40%-ot, még a jobb eredménnyel sem!
    • Érvényes nyilatkozat (megfelelően kitöltött statement.txt) hiányában a kapott értékelés érvénytelen, vagyis 0 pont.
    • Az elrontott, elfelejtett nyilatkozat utólag pótolható: Canvasen kommentben kell odaírni a feladathoz.
  • Egyéb
    • A feladatokat Node.js környezetben, JavaScript nyelven kell megoldani, a tantárgy keretein belül tanult technológiák használatával!
    • Ajánlott a Node.js LTS verziójának a használata. Ha a gépedre már telepítve van a Node.js és telepített példány legalább 2 főverzióval le van maradva az aktuálisan letölthető legfrissebbhez képest, akkor érdemes lehet frissíteni.

Hasznos linkek

Kezdőcsomag

Segítségképpen biztosítunk egy kezdőcsomagot a zárthelyihez. Csak telepíteni kell a csomagokat, és kezdheted is a fejlesztést.

GraphQL (15 pont)

A feladatot a kezdőcsomagon belül a graphql/graphql/typedefs.gql, graphql/graphql/resolvers.js fájlokba kell kidolgozni.

A kezdőcsomag két grafikus felületet is biztosít a GraphQL-hez, ezek az alábbi linkeken érhetők el:

Modellek és relációk

Az alábbi modelleket készen kapod, ami tartalmazza a következőket: migration, model, seeder. Tehát nem neked kell őket megírni, neked csak annyi a feladatod, hogy a készen kapott fájlokat bemásolod és inicializálod az adatbázist (npm run gql:db), hogy használni tudd! Az adatokat lokális SQLite adatbázisban kell tárolni.

Az id, createdAt, updatedAt a Sequelize ORM szempontjából alapértelmezett mezők, így ezeket a feladat nem specifikálja. Alapesetben egyik mező értéke sem lehet null, hacsak nem adtunk külön nullable kikötést! Tehát alapértelmezés szerint a migration minden mezőjére

allowNull: false

van érvényben, kivéve ott, ahol ezt a feladat másképp nem kéri!

A modellek az alábbiak:

  • User: felhasználó
    • id: integer, autoIncrement, primaryKey
    • name: string
    • email: string, unique
    • password: string
    • MembershipId: integer, nullable
    • createdAt: date
    • updatedAt: date
  • Book: könyv
    • id: integer, autoIncrement, primaryKey
    • author: string, nullable
    • title: string, nullable
    • description: string, nullable
    • numberOfCopies: integer, nullable
    • createdAt: date
    • updatedAt: date
  • Loan: kölcsönzés
    • id: integer, autoIncrement, primaryKey
    • UserId: integer
    • BookId: integer
    • expire: date
    • createdAt: date
    • updatedAt: date
  • Membership: tagság
    • id: integer, autoIncrement, primaryKey
    • name: string
    • maxLoans: integer
    • fine: integer
    • createdAt: date
    • updatedAt: date

A fenti modellek közötti relációk pedig a következőképpen alakulnak:

  • User 1-N Loan
  • Membership 1-N User
  • Book 1-N Loan

Típusdefiníció

Adott az alábbi GraphQL típusdefiníció. Másold be a kezdőcsomagba, ezt követően pedig implementálod a szükséges műveleteket. A műveletek implementálása során a típusdefiníciót értelemszerűen ki kell egészíteni, hiszen az alábbi csak egy kezdeti állapot.

type Query {
    info: InfoResult!
    availableBooks: [availableBookResult]
    userLoans(userId: Int!): [userLoanResult]
}

type Mutation {
    bookLend(userId: Int!, bookId: Int!): Date!
    bookReturn(userId: Int!, bookId: Int!): Int!
}

type availableBookResult {
    book: Book!
    numberOfAvailableCopies: Int!
}

type userLoanResult {
    book: Book
    expire: Date
    fine: Int
}

type InfoResult {
    name: String!
    neptun: String!
    email: String!
}

# Tagsági szint
type Membership {
    id: ID!
    name: String!    # A tagsági szint neve
    maxLoans: Int!   # Maximálisan kölcsönözhető könyvek száma
    fine: Int!       # Késedelmes visszaadás esetén a napi birság összege
    createdAt: Date!
    updatedAt: Date!
}

# Felhasználó
type User {
    id: ID!
    name: String!
    email: String!
    createdAt: Date!
    updatedAt: Date!

    # Asszociációk
    membership: Membership!
}

# Könyv
type Book {
    id: ID!
    author: String!      # A könyv szerzője
    title: String!       # A könyv címe
    description: String! # A könyv fülszövege (leírása)
    numberOfCopies: Int! # Példányszám, a könyvtár ennyi példánnyal rendelkezik ebből a könyvből
    createdAt: Date!
    updatedAt: Date!
}

# Kölcsönzések
type Loan {
    id: ID!
    expire: Date!    # A kölcsönzés lejárati dátuma, ezt követően napi bírságot kell fizetni a késedelmes visszaadásért
    createdAt: Date!
    updatedAt: Date!

    # Asszociációk
    user: User!
    book: Book!
}

1. feladat: info (1 pont)

Query: Írasd ki a nevedet, neptun kódodat és az egyetemi email címedet!

Kérés:

query {
  info {
    name
    neptun
    email
  }
}

Minta válasz: (a saját adataiddal)

{
  "data": {
    "info": {
      "name": "Németh Tamás",
      "neptun": "LX12AG",
      "email": "[email protected]"
    }
  }
}

2. feladat availableBooks (4 pont)

Query: Összes könyv lekérése, az elérhető (tehát ki nem vett) példányok számával.

Kérés:

query {
  availableBooks {
    book {
      title
      numberOfCopies
    }
    numberOfAvailableCopies
  }
}

Minta válasz:

{
  "data": {
    "availableBooks": [
      {
        "book": {
          "title": "qui",
          "numberOfCopies": 16
        },
        "numberOfAvailableCopies": 5
      },
      {
        "book": {
          "title": "ut dignissimos",
          "numberOfCopies": 0
        },
        "numberOfAvailableCopies": 0
      },
      {
        "book": {
          "title": "recusandae harum distinctio",
          "numberOfCopies": 7
        },
        "numberOfAvailableCopies": 0
      },
      {...}
    ]
  }
}

3. feladat userLoans (3 pont)

Query: Egy adott felhasználó által kikölcsönzött könyvek megjelenítése, a kölcsönzés lejáratának idejével és a mai napig fizetendő bírság összegével (ha még nem járt le a kölcsönzés, akkor a fine legyen 0).

Ha a userId nem létezik, dobj hibát!

A napi birság összegét a felhasználóhoz tartozó Membership-ből tudod kinyerni (Membership.fine)!

Tipp: Két dátum között eltelt nap meghatározásához az alábbi segédfüggvény használható:

/**
 * Kiszámítja a két dátum között eltelt időt.
 *
 * @param {String | Date} firstDate A kölcsönzés lejárati dátuma.
 * @param {String | Date} secondDate Alapértelmezetten az aktuális időpont.
 * @returns {Integer} A két dátum között eltelt napok száma. Értéke negatív is lehet!
 */
function daysBetweenDates(firstDate, secondDate = new Date()) {
    return Math.round((new Date(firstDate).setHours(12,0,0,0) - new Date(secondDate).setHours(12,0,0,0)) / (1000*3600*24));
}

Kérés:

query {
  userLoans(userId: 2) {
    expire
    fine
    book {
      author
      title
    }
  }
}

Minta válasz: Tfh.: aktuális dátum: 2021-12-11, felhasználó tagsága alapján a napi bírság: 50

{
  "data": {
    "userLoans": [
      {
        "book": {
          "author": "Miss Cora Wilkinson",
          "title": "magni nam esse"
        },
        "expire": "2021-12-09",
        "fine": 100
      },
      {
        "book": {
          "author": "Eileen Rippin",
          "title": "quas nostrum rerum"
        },
        "expire": "2021-12-08",
        "fine": 150
      },
      {
        "book": {
          "author": "Kurt Nitzsche",
          "title": "quia porro"
        },
        "expire": "2021-12-15",
        "fine": 0
      }
    ]
  }
}

4. feladat bookLend (4 pont)

Mutation: Egy könyv kikölcsönzése. A felhasználó és a könyv id-ját paraméteresen adjuk át, sikeres kölcsönzés esetén a visszavétel határidejét kapjuk meg. A határidő mindig az aktuális napot követő 14 nap.

Tipp: Egy adott időponthoz a következő segédfüggvénnyel lehet hozzáadni tetszőleges napot.

/**
 * Egy dátumhoz a paraméterben megadott napok számát adja hozzá.
 *
 * @param {Integer} days A hozzáadandó napok száma.
 * @param {String | Date} date A kiindulási dátum, melyhez a napokat hozzá akarjuk adni. Alapértelmezetten az aktuális időpont.
 * @returns {Date} A megadott napszámmal későbbi dátum.
 */
function addDays(days, date = new Date()) {
    return new Date(new Date(date).setHours(12,0,0,0) + (1000*3600*24*days));
}

Validáció:

  • Ellenőrizd, hogy a megadott userId és bookId helyesek-e, ha valamelyik rekord nem létezik, dobj értelmes hibát!
  • Ellenőrizd, hogy a felhasználó elérte-e már a tagsági szintje alapján (Membership.maxLoans) maximális egyidejű kölcsönzések számát. Ha igen, akkor dobj értelmes hibát!
  • Ellenőrizd, hogy van-e szabad példány a kikölcsönözni kívánt könyvből (Book.numberOfCopies[*már kikölcsönzött példányok száma*])! Ha nincs, dobj értelmes hibát!

Ha nem került dobásra hiba, akkor vegyél fel egy új rekordot a Loans táblába a megfelelő adatokkal és add vissza a visszahozási határidőt!

Kérés:

mutation {
  bookLend(userId: 1, bookId: 2)
}

Minta válasz: Tfh.: az aktuális dátum: 2021-12-11

{
  "data": {
    "bookLend": "2021-12-25"
  }
}

5. feladat bookReturn (3 pont)

Mutation: Egy könyv visszavétele. A felhasználó és a könyv id-ja paraméteresen kerül átadásra, sikeres visszavétel esetén a késedelmi birság összegét add meg! Ha időben lett visszaadva, térj vissza 0-ával!

Validáció:

  • Ellenőrizd, hogy a megadott userId és bookId helyesek-e, ha valamelyik rekord nem létezik, dobj értelmes hibát!
  • Ellenőrizd, hogy a megadott felhasználó kikölcsönözte-e a megadott könyvet (tartozik-e rekord a Loans táblában a megadott userId és bookId pároshoz)! Ha nem, dobj értelmes hibát!

Ha nem került dobásra hiba, akkor töröld a megfelelő rekordot a Loans táblából és add vissza a késedelmes visszavétel birságának összegét (napi birság díja a felhasználóhoz tartozó: Membership.fine). Ha a visszavétel nem volt késedelmes, térj vissza 0-ával!

Kérés:

mutation {
  bookReturn(userId: 10, bookId: 13)
}

Minta válasz:

{
  "data": {
    "bookReturn": 200
  }
}

Websocket/Socket.io (15 pont)

A feladatod egy chat elkészítése. A klienseknek legyen lehetősége belépni chatszobákba, ahol üzenetet tudnak küldeni a többi tagnak. Egy kliens saját szobát is csinálhat, ahová más kliensek csatlakozhatnak. Ha valaki létrehoz egy szobát, automatikusan a saját szobája adminjává válik, és jogában áll lenémítani tagokat, így ők onnantól kezdve nem írhatnak a többieknek abban a szobában.

Az adatokat a szerver memóriájában kell tárolni, az alábbi minta szerint:

let db = {
    rooms: {
        room1: {
            admin: "socket1",
            members: ["socket1", "socket2", "socket3"],
            muted: ["socket2"],
            messages: [
                { timestamp: 123456789, client: "socket1", message: "sziasztok" },
                { timestamp: 123456789, client: "socket3", message: "hali" },
            ],
        },
        "Másik szoba": {
            admin: "socket2",
            members: ["socket2"],
            muted: [],
            messages: [{ timestamp: 123456789, client: "socket2", message: "foreveralone" }],
        },
    },
};

Ezekre figyelj, hogy a tesztelő működjön:

  • Az adatokat a db-be tárold és onnan is olvasd ki.
  • A fenti példa elnevezéseit és felépítését kövesd. Pl: a muted maradjon muted, NE legyen pl. mutedMembers!
  • A végpontoknak két paramétere legyen, ahogy gyakorlatokon tanultuk: data és ack, ahol a data egy objektum, ami tartalmazza az összes bemenő adatot.
  • Pontosan kövesd a feladatokban megadott hibaüzeneteket.

A feladatot a kezdőcsomagon belül a websocket/events.js fájlba kell kidolgozni.

1. feladat: list-rooms (1 pont)

  • Elérhető chat szobák listázása
  • Paraméterek: -
  • Válasz (acknowledgement):
    • Jó esetben: { status: 'ok', rooms: ['room1', 'room2', ...] }
    • Hiba esetén: { status: 'error', message: '<hibaüzenet>'}

2. feladat: create-room (2 pont)

  • Szoba létrehozása. Ilyenkor aki létrehozza a szobát, automatikusan csatlakozik hozzá, illetve az adminjává is válik. A kliens csatlakozását és admin jogát az adatbázisban le kell tárolni, továbbá SocketIO szinten is hozzá kell őt adni a szobához.
  • Paraméterek
    • room: a létrehozandó szoba neve
  • Válasz (acknowledgement):
    • Jó esetben: { status: 'ok' }
    • Hiba esetén:
      • Hiányzó, rossz paraméter: { status: 'error', message: '<hibaüzenet>'}
      • A megadott névvel már létezik szoba: { status: 'error', message: 'This name is already taken!'}

3. feladat: join-room (1 pont)

  • Csatlakozás egy chatszobához. A kliens csatlakozását az adatbázisban is le kell tárolni, továbbá SocketIO szinten is hozzá kell őt adni a szobához.
  • Paraméterek
    • room: a szoba neve, amihez csatlakozni szeretnénk
  • Válasz (acknowledgement):
    • Jó esetben: { status: 'ok' }
    • Hiba esetén:
      • Hiányzó, rossz paraméter: { status: 'error', message: '<hibaüzenet>'}
      • Nem létező szoba: { status: 'error', message: 'No such room in our system!'}
      • A kliens már a szoba tagja: { status: 'error', message: 'You are already subscribed to this room!'}

4. feladat: mute-client (5 pont)

  • Az adminnak legyen lehetősége lenémítani egy klienst, aki az ő szobájában van.
  • Paraméterek
    • room: a szoba neve, amelyben némítani szeretnénk
    • clientId: a némítandó kliens SocketIO azonosítója
    • reason: a némítás oka (opcionális)
  • Válasz (acknowledgement):
    • Jó esetben:
      • { status: 'ok' }
      • Továbbá a lenémított kliensnek el kell küldeni a muted üzenetet, a következő adatokkal (minta):
        • { room: 'room1', admin: 'socket1', reason: 'káromkodtál' }
        • Ha nem volt reason megadva, az értéke legyen "Nincs indok"
    • Hiba esetén:
      • Hiányzó, rossz paraméter: { status: 'error', message: '<hibaüzenet>'}
      • Nem létező szoba: { status: 'error', message: 'No such room in our system!'}
      • A kliens nem a szoba tagja: { status: 'error', message: 'You are not subscribed to this room!'}
      • A kliens nem admin a szobában: { status: 'error', message: 'You have no power here, Gandalf the Grey!'}
      • A kliens saját magát akarja némítani: { status: 'error', message: 'You can't mute yourself!'}
      • A célpont nem tagja a szobának: { status: 'error', message: 'The target is not a member of the room!'}
      • A célpont már némítva van: { status: 'error', message: 'The target is already muted!'}

5. feladat: send-message (6 pont)

  • Üzenet küldése a chat szoba tagjainak, ha nem vagyunk némítva.
  • Paraméterek
    • room: a szoba neve, amelyben üzenni szeretnénk
    • message: üzenet tartalma
  • Válasz (acknowledgement):
    • Jó esetben:
      • { status: 'ok' }
      • Továbbá a szobában lévő összes kliensnél (de csak náluk) message-received eseményt kell kiváltani, a következő adatokkal (minta):
        • { room: 'room1', timestamp: 123456789, client: 'socket1', message: 'sziasztok' }
    • Hiba esetén:
      • Hiányzó, rossz paraméter: { status: 'error', message: '<hibaüzenet>'}
      • Nem létező szoba: { status: 'error', message: 'No such room in our system!'}
      • A kliens nem a szoba tagja: { status: 'error', message: 'You are not subscribed to this room!'}
      • A kliens némítva van: { status: 'error', message: 'You are muted!'}