Meeskond:Curry

From ICO wiki

Liikmed

  • Rain Elken
  • Peeter Ploom

Analüüs

Projekti kirjeldus

Curry näol on tegemist erinevate krüptorahade kauplemisplatvormiga, kus registreerunud kasutaja saab üles seada ostu- või müügitellimusi (edaspidi ordereid). Ostja määrab ära, millise maksimaalse hinna eest on nõus ühte krüptoraha valuutat teise eest ostma ning ka ostukoguse. Müüa seevastu ütleb, millise hinnaga soovib ühte valuutat teise vastu vahetada ja koguse. Süsteem viib automaatselt ostu-müügiorderid kokku ning kui hind on mõlemapoolselt sobilik, toimub tehing. Platvorm haldab ise kasutajakontol olevat rahakogust. Rakenduse peavaates on võimalik näha aktiivseid ordereid, kus müügiorderid reastatakse hinna järgi kasvavalt, ostuorderid kahanevalt. Projekti mahu hulka ei kuulu krüptovaluutade sisse- ja väljakande funktsionaalsus.

Andmemudeli skeem:

Curry.png


Üldised nõuded:

  • Veebiteenuse andmevahetus toimub JSON formaadis.
  • Klientrakendus töötab kõikides enamlevinud veebibrauserites.


Must-have funktsionaalsus:

  • Kasutaja saab endale registreerimisega konto luua.
  • Kasutaja saab enda andmeid (nimi, email, jne) muuta.
  • Süsteemis on kindlaks määratud kindlad valuutapaarid, millede vahel kaubelda saab. Minimaalselt 1 valuutapaar ehk 2 erinevat valuutat.
  • Kasutaja saab sisestada limiit müügiordereid, kus märgib ära millist valuutat mille vastu müüb, ning määrab hinna ning koguse.
  • Kasutaja saab sisestada limiit ostuordereid, kus märgib ära millist valuutat soovib osta mõne teise valuuta eest ning määrab maksimaalse ostuhinna ning koguse.
  • Süsteem leiab ostu-müügi orderite vahel kokku sobivad orderid, mis hinna poolest rahuldavad mõlemaid osapooli ning toimub automaatselt tehing – orderid loetakse kas osaliselt või täielikult täidetuks ning tehing kantakse tehingute nimekirja. Vastavatel osapooltel toimub ka kontoseisu muutus.
  • Orderite täitmine võib toimuda ka mitmes osas, kus ühe osapoole valuuta kogus erineb teise osapoole orderil märgitud kogusest. Sel juhul vähendatakse ühe osapoole orderi kogust toimunud tehingu mahu võrra.
  • Kasutaja saab tühistada enda poolt üles seatud ordereid.
  • Kasutaja saab vaadata rahakonto seisu, kui palju erinevaid valuutasid arvel on.
  • Kasutaja saab vaadata enda orderite ja tehingute ajalugu.
  • Iga toimunud tehingu pealt võetakse ka % teenustasu.
  • Orderi loomise eelnevalt kontrollitakse, kas kasutaja rahakontol piisavalt vabu vahendeid tehingu toimimiseks ning kuni tehingu toimumiseni on vastav kogus valuutat kontol broneeritud. Reaalselt arvestatakse maha peale tehingu lõppu.
  • Admin saab määrata, milliste süsteemis olevate valuutade vahel saab kaubelda.
  • Admin saab muuta teenustasu % väärtust.
  • Admin saab lukustada kasutajate kontosid.


Nice-to-have funktsionaalsus:

  • Valuutade sisse -ja väljakanded.
  • Graafiline kasutajaliides hinnaliikumise ja teiste näitajate kuvamiseks.
  • Peale limiitorderite ka teiste orderitüüpide toetamine.

Juhend lõpptoote kasutamiseks:

  • Proovimaks administraator õigustes kasutaja funktsionaalsust, logi sisse kasutajanimega 'adminuser' ning parooliga 'Kala1.maja'

XML andmefail

<?xml version="1.0" encoding="utf-8"?>
<Countries xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Capitals>
    <Capital id="capital1">
      <Name>Tallinn</Name>
      <Area>100</Area>
    </Capital>
    <Capital id="capital2">
      <Name>Riia</Name>
      <Area>101</Area>
    </Capital>
    <Capital id="capital3">
      <Name>Vilnius</Name>
      <Area>102</Area>
    </Capital>
    <Capital id="capital4">
      <Name>Helsingi</Name>
      <Area>103</Area>
    </Capital>
    <Capital id="capital5">
      <Name>Stockholm</Name>
      <Area>104</Area>
    </Capital>
    <Capital id="capital6">
      <Name>Kopenhaagen</Name>
      <Area>105</Area>
    </Capital>
  </Capitals>
  <Towns>
    <Town id="town1">
      <Name>Tartu</Name>
      <Founded>1000</Founded>
    </Town>
    <Town id="town2">
      <Name>Tampere</Name>
      <Founded>1100</Founded>
    </Town>
    <Town id="town3">
      <Name>Jelgava</Name>
      <Founded>1300</Founded>
    </Town>
    <Town id="town4">
      <Name>Kaunas</Name>
      <Founded>1500</Founded>
    </Town>   
    <Town id="town5">
      <Name>Norrköping</Name>
      <Founded>1700</Founded>
    </Town>
    <Town id="town6">
      <Name>Aalborg</Name>
      <Founded>1700</Founded>
    </Town>
    <Town id="town7">
      <Name>Pärnu</Name>
      <Founded>1800</Founded>
    </Town>
    <Town id="town8">
      <Name>Valmiera</Name>
      <Founded>1900</Founded>
    </Town>
    <Town id="town9">
      <Name>Malmö</Name>
      <Founded>1400</Founded>
    </Town>
    <Town id="town10">
      <Name>Linköping</Name>
      <Founded>1200</Founded>
    </Town>
  </Towns>
  <Country id="country1">
    <Name>Eesti</Name>
    <Capital refid="capital1"/>
    <CountryTowns>
      <Town refid="town1"/>
      <Town refid="town7"/>
    </CountryTowns>
    <NeighbouringCountries>
      <NeighbouringCountry>
        <Country refid ="country2"/>
        <Country refid ="country3"/>
        <Country refid ="country5"/>
      </NeighbouringCountry>
    </NeighbouringCountries>
  </Country>
  <Country id="country2">
    <Name>Soome</Name>
    <Capital refid="capital4"/>
    <CountryTowns>
      <Town refid="town2"/>
    </CountryTowns>
    <NeighbouringCountries>
      <NeighbouringCountry>
        <Country refid ="country1"/>
        <Country refid ="country5"/>
      </NeighbouringCountry>
    </NeighbouringCountries>
  </Country>
  <Country id="country3">
    <Name>Läti</Name>
    <Capital refid="capital2"/>
    <CountryTowns>
      <Town refid="town3"/>
      <Town refid="town8"/>
    </CountryTowns>
    <NeighbouringCountries>
      <NeighbouringCountry>
        <Country refid ="country1"/>
        <Country refid ="country4"/>
      </NeighbouringCountry>
    </NeighbouringCountries>
  </Country>
  <Country id="country4">
    <Name>Leedu</Name>
    <Capital refid="capital3"/>
    <CountryTowns>
      <Town refid="town4"/>
    </CountryTowns>
    <NeighbouringCountries>
      <NeighbouringCountry>
        <Country refid ="country3"/>
      </NeighbouringCountry>
    </NeighbouringCountries>
  </Country>
  <Country id="country5">
    <Name>Rootsi</Name>
    <Capital refid="capital5"/>
    <CountryTowns>
      <Town refid="town5"/>
      <Town refid="town9"/>
      <Town refid="town10"/>
    </CountryTowns>
    <NeighbouringCountries>
      <NeighbouringCountry>
        <Country refid ="country6"/>
        <Country refid ="country1"/>
        <Country refid ="country2"/>
      </NeighbouringCountry>
    </NeighbouringCountries>
  </Country>
  <Country id="country6">
    <Name>Taani</Name>
    <Capital refid="capital1"/>
    <CountryTowns>
      <Town refid="town6"/>
    </CountryTowns>
    <NeighbouringCountries>
      <NeighbouringCountry>
        <Country refid ="country5"/>
      </NeighbouringCountry>
    </NeighbouringCountries>
  </Country>
</Countries>


XSD schema fail

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:simpleType name="TypeName">
    <xs:restriction base="xs:string">
      <xs:minLength value="3" />
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="TypeArea">
    <xs:restriction base="xs:integer">
      <xs:minInclusive value="100" />
    </xs:restriction>
  </xs:simpleType>
  <xs:element name="Countries">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Capitals">
          <xs:complexType>
            <xs:sequence>
              <xs:element maxOccurs="unbounded" name="Capital">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element type="TypeName" name="Name"/>
                    <xs:element type ="TypeArea" name="Area"/>
                  </xs:sequence>
                  <xs:attribute name="id" type="xs:ID"/>
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Towns">
          <xs:complexType>
            <xs:sequence>
              <xs:element maxOccurs="unbounded" name="Town">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="Name" type="xs:string" />
                    <xs:element name="Founded" type="xs:unsignedShort" />
                  </xs:sequence>
                  <xs:attribute name="id" type="xs:ID" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element maxOccurs="unbounded" name="Country">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Name" type="TypeName"/>
              <xs:element name="Capital">
                <xs:complexType>
                  <xs:attribute name="id" type="xs:ID"/>
                  <xs:attribute name="refid" type="xs:IDREF"/>
                </xs:complexType>
              </xs:element>
              <xs:element name="CountryTowns">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element maxOccurs="unbounded" name="Town">
                      <xs:complexType>
                        <xs:attribute name="refid" type="xs:IDREF"/>
                      </xs:complexType>
                    </xs:element>
                  </xs:sequence>
                </xs:complexType>
                <xs:unique name="TownUnique ">
                  <xs:selector xpath="Town"/>
                  <xs:field xpath="@refid"/>
                </xs:unique>
              </xs:element>
              <xs:element name="NeighbouringCountries">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="NeighbouringCountry">
                      <xs:complexType>
                        <xs:sequence>
                          <xs:element maxOccurs="unbounded" name="Country">
                            <xs:complexType>
                              <xs:attribute name="refid" type="xs:IDREF"/>
                            </xs:complexType>
                          </xs:element>
                        </xs:sequence>
                      </xs:complexType>
                    </xs:element>
                  </xs:sequence>
                </xs:complexType>
              </xs:element>
            </xs:sequence>
            <xs:attribute name="id" type="xs:ID" />         
          </xs:complexType>
          <xs:key name="PK_Capitals">
            <xs:selector xpath="Countries/Capital"/>
            <xs:field xpath="@id"/>
          </xs:key>
          <xs:keyref name="FK_CountryCapitals" refer="PK_Capitals">
            <xs:selector xpath="Countries/Country/Capital"/>
            <xs:field xpath="@refid"/>
          </xs:keyref>
          <xs:key name="PK_Towns">
            <xs:selector xpath="Countries/Towns/Town"/>
            <xs:field xpath="@id"/>
          </xs:key>
          <xs:keyref name="FK_CountryTowns" refer="PK_Towns">
            <xs:selector xpath="Countries/Country/CountryTowns/Town"/>
            <xs:field xpath="@refid"/>
          </xs:keyref>
          <xs:key name="PK_Countries">
            <xs:selector xpath="Countries/Country"/>
            <xs:field xpath="@id"/>
          </xs:key>
          <xs:keyref name="FK_Countries" refer="PK_Countries">
            <xs:selector xpath="Countries/Country/NeighbouringCountries/NeighbouringCountry/Country"/>
            <xs:field xpath="@refid"/>
          </xs:keyref>        
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>


XSLT transformatsioon: XML -> HTML

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="capital" match="Countries/Capitals/Capital" use="@id" />
  <xsl:key name="town" match="Countries/Towns/Town" use="@id" />
  <xsl:key name="country" match="Countries/Country" use="@id" />
  <xsl:template match="/">
    <html>
      <body>
        <h2 style="text-align:center">Countries</h2>
        <table align="center" border="1">
          <tr bgcolor="#9acd32">
            <th style="text-align:left">Country name</th>
            <th style="text-align:left">Capital</th>
            <th style="text-align:left">Towns</th>
            <th style="text-align:left">Neighbouring countries</th>
          </tr>
            <xsl:for-each select="Countries/Country">
              <tr>
                <td>
                  <xsl:value-of select="Name"/>
                </td>
                <td>
                  <xsl:for-each select="key('capital', Capital/@refid)">
                  <xsl:value-of select="Name"/>
                  </xsl:for-each>
                </td>
                <td>
                  <xsl:for-each select="key('town', CountryTowns/Town/@refid)">
                  <xsl:value-of select="Name"/>
                    <xsl:if test="position() != last()">
                    <xsl:text>, </xsl:text>
                    </xsl:if>
                  </xsl:for-each>
                </td>
                <td>
                  <xsl:for-each select="key('country', NeighbouringCountries/NeighbouringCountry/Country/@refid)">
                  <xsl:value-of select="Name"/>
                    <xsl:if test="position() != last()">
                    <xsl:text>, </xsl:text>
                    </xsl:if>
                  </xsl:for-each>
                </td>
              </tr>
            </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

XSLT transformatsioon: XML -> XML

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>
  <xsl:key name="town" match="Countries/Towns/Town" use="@id" />
  <xsl:key name="country" match="Countries/Country" use="@id" />
  <xsl:template match="/">
    <Countries>
      <xsl:for-each select="Countries/Country">
        <name>
          <xsl:value-of select="Name"/>
        </name>
        <Towns>
          <xsl:for-each select="key('town', CountryTowns/Town/@refid)">          
            <Town>
              <xsl:attribute name="Founded">
                <xsl:value-of select="Founded"/>
              </xsl:attribute>
            <xsl:value-of select="Name"/>
            </Town>
          </xsl:for-each>      
        </Towns>
        <NumberOfNeighbouringCountries>
            <xsl:value-of select="count(key('country', NeighbouringCountries/NeighbouringCountry/Country/@refid))"/>
        </NumberOfNeighbouringCountries>
      </xsl:for-each>
    </Countries>
  </xsl:template>
</xsl:stylesheet>

Meie retsensioonid BestInShow meeskonnale

Veebiteenus

  • Meeskonna BestInShow veebiteenused on loodud REST teenustena, toetamaks edaspidise rakenduse andmevahetust. Probleeme käivitamisel ei ole – Wiki lehel on äratoodud käivitamisjuhised. Teenused on ilusasti dokumenteeritud, ära on kirjeldatud iga teenuse juures tema võimalikud tagastusväärtused HTTP staatuskoodidena, olgu see näiteks et vastavad andmed leiti või ei leitud või oli hoopis tegemist vigase päringuga. Ilusasti töötab teenuste kirjeldamiseks mõeldud raamistik Swagger. Kogu veebiteenuse projekt on jaotatud loogilisteks alamprojektideks, kus eraldi on Business logic, Data Access layer, Domain. Kasutatud on kenasti kursuse käigus õpitud arhitektuurimustrit, kus on eraldi Service klassid, DTO-d, Factory-d, Repository-d, UnitOfWork jne. Teenuste kontrollerid on üldjuhul ’puhtad’, äriloogikat neisse kirjeldatud ei ole, vaid on edasi delegeeritud Service ja sealt edasi teistesse komponentidesse. Nii DTO-de kui ka Domain klassides on kasutatud andmeväljade juures annotatsioone, piiramaks andmebaasis väljade ning ka teenuse poolel sissetulevate andmete suurusi.
  • Probleemina võib välja tuua, et igaüks võib luua endale ’admin’ kontot – kasutaja roll on kirjutatud DTO juurde ning saab vabalt postitada uue kasutaja admin õigustega. Rollide haldamiseks oleks soovitav kasutada eraldi klassi RoleManager. Autentimistokeni loomise käigus isegi kirjutatakse kasutaja rollid tema sisse, kuid kuna neid tegelikkuses Identity raamistiku mõistes ei ole, ei kirjutata ka sinna midagi. Selle kasutamine võimaldaks lihtsamini piirata, mis teenused mis rollidele kättesaadavad on. Küsimust tekitab SecurityControlleris Logout meetod, mis ei tee midagi kuid mille poole ka klientrakenduses pöördutakse. Kuna tokenipoolne autentimine on olemuselt stateless, ei saa vähemalt sedasi serveripoolelt välja logida. See on mõeldud cookie hävitamiseks, mida aga ka eelnevalt loodud ei ole.
  • Norida saab sellega, et projektis läbivalt ei ole kasutatud Enum tüüpi väärtuseid, vaid on kirjutatud vajalikes kohtades väärtused lahtise stringina (!user.Role.Equals("admin")) või hoopis mõni asi numbrina (user.Status == 1). Üheltpoolt see võib põhjustada vigu, teiseltpoolt teeb teisel arendajal arusaamise keerulisemaks.
  • Boonusena tooks välja, et kõik API poolsed requestid logitakse andmebaasi, kus on kirjas, mis teenuse poole pöörduti, milliselt aadressilt, kellaaeg ja kasutaja või selle puudumisel anonüümne isik. Selleks kasutatakse ActionFilter klassi, mis lisatakse ServiceFilter atribuudina igale kontrollerile juurde.

Kokkuvõttes on tehtud hea ja tubli töö, on näha nii projektist endast kui ka Wiki lehelt, et on asjaga pidevalt ja süsteemselt tegeletud ning vajalikud teadmised REST veebiteenuste loomiseks on omandatud.


Klientrakendus

  • Meeskond BestInShow on loonud klientrakenduse tõukoerte registri kohta, kus kasutatakse ära enda poolt loodud REST veebiteenuseid. Rakendus on loodud Angular raamistikus ning kasutatud on selle viimast versiooni 6.0. Juba ainuüksi Angulari valimine on julge väljakutse, arvestades et käesoleva kursuse raames pidi selle osa täielikult iseseisvalt omandama.
  • Meeskonna Wiki lehel oli olemas kena õpetus, kuidas rakendus käima panna, veebiteenus ja klientrakendus töötavad erinevates projektides. Kaasa oli pandud rakenduse toimimiseks vajalikud algandmed sql-failis, mis tuli pärast andmebaasi loomist sisse laadida. Mingeid tõrkeid käivitamisel ei esinenud. Olles esilehele jõudnud, pakutakse võimalust kas sisse logida või uus kasutaja luua. Selgub et, sisselogimata kasutaja suurt midagi muud teha ei saagi. Analüüsi funktsionaalsetes nõuetes on kirjeldatud, et registreerimata kasutaja saab sirvida kenneleid, tõugusid, pesakond ja koeri. Rakenduse juhendiga on kaasa pandud mõned valmiskasutajad koos vajalike paroolidega. Uue kasutaja registreerimine toimib hästi, kõikide väljade täidetust valideeritakse ning antakase teada probleemist, kui ei ole sobilikult täidetud või kui juba sama kasutajanimi esineb. Seejuures on hästi kasutatud nii registreerimisvormil kui ka muudes kohtades edukalt Bootstrap võimalusi. Sisseloginuna kuvatakse teade, et mul ei ole veel koeri kuid puudub ka nupp koerte lisamiseks. Koeri saab lisada, kui kasutada juba loodud kasutajakontot ’breeder’ rollis. Ei oska öelda, kas see peakski nii olema, kuid võiks eeldada, et tavaõigustes kasutaja peaks saama ka omale koera registreerida. Koera detailvaate kuvamise funktsionaalsus on jäänud realiseerimata (selgub koodile peale vaadates), toimib olemasoleva koera andmete muutmine. Loodud on võimalus oma koera teisele omanikule anda, selleks avaneb eraldi modal aken, kuhu tuleb sisse kirjutada uus omaniku kasutajanimi. Siinkohal on probleem, et peab teadma täpset teist kasutajanime, võiks olla näiteks dropdown menüüst valitav võimalike kasutajate hulga seast. Kui sisestada suvaline nimi, ei anta teada, et sellist kasutajanime ei eksisteerida, konsoolist olevast errorist saab aru et ei õnnestunud. ’Breeder’ ja ’Owner’ rolliga saab esitada taotluse uue kenneli loomiseks, kus saab määrata, mis tõugu koeri on võimalik seal kasvatada. Need taotlused jõuavad admin õigustes kasutaja töölauale ning kui taotlus saab aktsepteeritud, jõuab vastav kennel ka kennelite nimekirja. Tundub, et edasine funktsionaalsus kennelite osas on jäänud realiseerimata, vähemalt ei paista kusagilt välja et midagi nende kennelitega teha saaks, sinna koeri paigutada vms.
  • Kõige häirivam probleem, on et brauseri refreshimisel läheb sisse loginud kasutaja jaoks rakendus katki – ’currentUser’ muutuja ei saa enam laadimisel väärtustamisest ning kui teistes komponentides viidatakse sellele muutujale, ei saada sealt enam väärtust kätte. See ’currentUser’ võiks olla Observable tüüpi ning komponenti laadimisel võiks ta olla väärtustatud olemasoleva localStorages talletatud tokeni seest dekodeerimisel väljaloetud kasutajanime järgi.
  • Hea külg on, et rakenduse ülesehitusel on kasutatud loogilist komponentideks ja alamkomponentideks jagamist ning head arendusmustrit, kus on eraldi service-klassid API teenuste poole pöördumiseks, eraldi on rakenduses kasutatavad mudelid. Stiilifailid eraldi css failidena. Selline struktuuri järgimine teeb koodi lugemise ja vajaliku leidmise lihtsaks. Sisseloginud kasutaja jaoks, kel on olemas vastav token, pannakse interceptor meetodi abil iga päringuga päringu headeris automaatselt kaasa tema token. Implementeeritud on ’AuthGuard’ meetod, ehk piiratakse millised rakenduses olevad leheküljed on mõeldud vaatamiseks ainult sisseloginud kasutajale ehk neile kes omavad tokenit.

Kokkuvõttes on mõningatele puudustele vaatamata tehtud hea ning kiiduväärt töö.

XML osa

  • Meeskonna BestInShow XML failis olevate andmete koosseis tuleneb nende projekttööst, ehk on tegemist rahvusvahelise tõukoerte näitusega, kus on mitmes kihis ära näidatud erinevate tõugude esindatus ning välja on toodud igat tõugu esindavad koerad ning ja omaniku andmed ning riiklik päritolu. Loogilise dimensioone on rohkem kui 4 ehk see arvuline nõue on täidetud. Kasutatud on mitmes kohaks lisaks atribuuttunnuseid. Mitmed nimi-tüüpi väärtusi saab esitada on mitmekeelselt, kus vastava elemendi keele määratlemine käib atribuuttunnuse abil.
  • XSD schemafail ja XML ise on omavahel kooskõlas, XSD-s on kasutatud kenasti piiravaid kriteeriume, et ühel objektil ei saa olla näiteks rohkem kui 2 nime ning on märgitud, millised atribuudid millisel objektil olema peavad.
  • Realiseeritud on 2 on XSLT transformatsiooni, üks teeb HTML vormingus tabeli, teine teise XML struktuuriga faili. HTML transformatsioonis on esitatud erinevate võistlusklasside võitjad, tegemist väga ilusalt vormindatud tabeliga. Kasutatud on andmete kokkusaamiseks mitmeid if-tingimusi ning tsükleid, loendatud on igas võistlusklassis olevate osalejate arv ning välja toodud selle võitja. Teises teiseks XML transformatsioonis filtreeritakse välja ainult ’Best in Show’ ja ’Best of Breed’ võitnud koerad, tuuakse välja koeraomanik.

Kokkuvõttes on XML osa hästi tehtud ning paraja keerukusega.


Tegevuste logi:

10.02.2018 - Meeskonna loomine, idee paikapanemine.

24.03.2018 - Andmemudeli koostamine.

26.03.2018 - Analüüsi ja funktsionaalsuse kirjutamine.

31.03.2018 - Wiki lehe loomine.

02.04.2018 - Koodi kirjutamise alustamine, Giti repo loomine

14.05.2018 - Angular klientrakenduse alustamine

08.06.2018 - Lõpptoode valmis

09.06.2018 - XML töö valmis

11.06.2018 - retsensioonid tehtud