├── README.md ├── anecdotes.json ├── better-patients.ts ├── blogs_for_test.md ├── contributors.md ├── diagnoses.json ├── diagnoses.ts ├── diaryentries.json ├── dokumenttitietokannat.MD ├── fly_io_problem.md ├── harjoitustyo-hy.md ├── harjoitustyo.md ├── library-backend.js ├── library-schema.md ├── notes-bootstrap.js ├── notes-materialui.js ├── patients-full.ts ├── patients.json ├── patients.ts ├── project.md ├── router-app-v1.js └── router-app-v2.js /README.md: -------------------------------------------------------------------------------- 1 | # diagrams 2 | 3 | ## part0b, 1st diagram 4 | 5 | ```mermaid 6 | sequenceDiagram 7 | participant browser 8 | participant server 9 | browser->>server: GET https://studies.cs.helsinki.fi/exampleapp/ 10 | activate server 11 | server-->>browser: HTML document 12 | deactivate server 13 | browser->>server: GET https://studies.cs.helsinki.fi/exampleapp/kuva.png 14 | activate server 15 | server-->>browser: the png pic 16 | deactivate server 17 | Note right of browser: Page with a pic will is rendered 18 | ``` 19 | 20 | ## part0b, 2nd diagram 21 | 22 | ```mermaid 23 | sequenceDiagram 24 | participant browser 25 | participant server 26 | 27 | browser->>server: GET https://studies.cs.helsinki.fi/exampleapp/notes 28 | activate server 29 | server-->>browser: HTML document 30 | deactivate server 31 | 32 | browser->>server: GET https://studies.cs.helsinki.fi/exampleapp/main.css 33 | activate server 34 | server-->>browser: the css file 35 | deactivate server 36 | 37 | browser->>server: GET https://studies.cs.helsinki.fi/exampleapp/main.js 38 | activate server 39 | server-->>browser: the JavaScript file 40 | deactivate server 41 | 42 | Note right of browser: The browser starts executing the JavaScript code that fetches the JSON from the server 43 | 44 | browser->>server: GET https://studies.cs.helsinki.fi/exampleapp/data.json 45 | activate server 46 | server-->>browser: [{ "content": "HTML is easy", "date": "2023-1-1" }, ... ] 47 | deactivate server 48 | 49 | Note right of browser: The browser executes the callback function that renders the notes 50 | ``` 51 | 52 | ## part4d, diagram 53 | 54 | ```mermaid 55 | sequenceDiagram 56 | participant user 57 | participant browser 58 | participant backend 59 | 60 | Note left of user: User fills in login form with username and password 61 | user->>browser: login button pressed 62 | 63 | activate backend 64 | browser->>backend: HTTP POST /api/login { username, password } 65 | 66 | Note left of backend: backend generates a TOKEN that identifies the user 67 | backend-->>browser: TOKEN returned as message body 68 | deactivate backend 69 | 70 | Note left of browser: browser saves the TOKEN 71 | 72 | Note left of user: User creates a note 73 | user ->> browser: create note button pressed 74 | 75 | activate backend 76 | browser ->> backend: HTTP POST /api/notes { content } TOKEN in header 77 | Note left of backend: backend identifies the user from the TOKEN 78 | 79 | backend -->> browser: 201 created 80 | deactivate backend 81 | ``` 82 | -------------------------------------------------------------------------------- /anecdotes.json: -------------------------------------------------------------------------------- 1 | { 2 | "anecdotes":[ 3 | { 4 | "content": "If it hurts, do it more often", 5 | "id": "47145", 6 | "votes": 0 7 | }, 8 | { 9 | "content": "Adding manpower to a late software project makes it later!", 10 | "id": "21149", 11 | "votes": 0 12 | }, 13 | { 14 | "content": "The first 90 percent of the code accounts for the first 10 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.", 15 | "id": "69581", 16 | "votes": 0 17 | }, 18 | { 19 | "content": "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.", 20 | "id": "36975", 21 | "votes": 0 22 | }, 23 | { 24 | "content": "Premature optimization is the root of all evil.", 25 | "id": "25170", 26 | "votes": 0 27 | }, 28 | { 29 | "content": "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.", 30 | "id": "98312", 31 | "votes": 0 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /better-patients.ts: -------------------------------------------------------------------------------- 1 | import { Patient, Gender } from '../src/types'; 2 | 3 | const patients: Patient[] = [ 4 | { 5 | id: 'd2773336-f723-11e9-8f0b-362b9e155667', 6 | name: 'John McClane', 7 | dateOfBirth: '1986-07-09', 8 | ssn: '090786-122X', 9 | gender: Gender.Male, 10 | occupation: 'New york city cop', 11 | entries: [ 12 | { 13 | id: 'd811e46d-70b3-4d90-b090-4535c7cf8fb1', 14 | date: '2015-01-02', 15 | type: 'Hospital', 16 | specialist: 'MD House', 17 | diagnosisCodes: ['S62.5'], 18 | description: 19 | "Healing time appr. 2 weeks. patient doesn't remember how he got the injury.", 20 | discharge: { 21 | date: '2015-01-16', 22 | criteria: 'Thumb has healed.', 23 | }, 24 | }, 25 | ], 26 | }, 27 | { 28 | id: 'd2773598-f723-11e9-8f0b-362b9e155667', 29 | name: 'Martin Riggs', 30 | dateOfBirth: '1979-01-30', 31 | ssn: '300179-777A', 32 | gender: Gender.Male, 33 | occupation: 'Cop', 34 | entries: [ 35 | { 36 | id: 'fcd59fa6-c4b4-4fec-ac4d-df4fe1f85f62', 37 | date: '2019-08-05', 38 | type: 'OccupationalHealthcare', 39 | specialist: 'MD House', 40 | employerName: 'HyPD', 41 | diagnosisCodes: ['Z57.1', 'Z74.3', 'M51.2'], 42 | description: 43 | 'Patient mistakenly found himself in a nuclear plant waste site without protection gear. Very minor radiation poisoning. ', 44 | sickLeave: { 45 | startDate: '2019-08-05', 46 | endDate: '2019-08-28', 47 | }, 48 | }, 49 | ], 50 | }, 51 | { 52 | id: 'd27736ec-f723-11e9-8f0b-362b9e155667', 53 | name: 'Hans Gruber', 54 | dateOfBirth: '1970-04-25', 55 | ssn: '250470-555L', 56 | gender: Gender.Male, 57 | occupation: 'Technician', 58 | entries: [], 59 | }, 60 | { 61 | id: 'd2773822-f723-11e9-8f0b-362b9e155667', 62 | name: 'Dana Scully', 63 | dateOfBirth: '1974-01-05', 64 | ssn: '050174-432N', 65 | gender: Gender.Female, 66 | occupation: 'Forensic Pathologist', 67 | entries: [ 68 | { 69 | id: 'b4f4eca1-2aa7-4b13-9a18-4a5535c3c8da', 70 | date: '2019-10-20', 71 | specialist: 'MD House', 72 | type: 'HealthCheck', 73 | description: 'Yearly control visit. Cholesterol levels back to normal.', 74 | healthCheckRating: 0, 75 | }, 76 | { 77 | id: 'fcd59fa6-c4b4-4fec-ac4d-df4fe1f85f62', 78 | date: '2019-09-10', 79 | specialist: 'MD House', 80 | type: 'OccupationalHealthcare', 81 | employerName: 'FBI', 82 | description: 'Prescriptions renewed.', 83 | }, 84 | { 85 | id: '37be178f-a432-4ba4-aac2-f86810e36a15', 86 | date: '2018-10-05', 87 | specialist: 'MD House', 88 | type: 'HealthCheck', 89 | description: 90 | 'Yearly control visit. Due to high cholesterol levels recommended to eat more vegetables.', 91 | healthCheckRating: 1, 92 | }, 93 | ], 94 | }, 95 | { 96 | id: 'd2773c6e-f723-11e9-8f0b-362b9e155667', 97 | name: 'Matti Luukkainen', 98 | dateOfBirth: '1971-04-09', 99 | ssn: '090471-8890', 100 | gender: Gender.Male, 101 | occupation: 'Digital evangelist', 102 | entries: [ 103 | { 104 | id: '54a8746e-34c4-4cf4-bf72-bfecd039be9a', 105 | date: '2019-05-01', 106 | specialist: 'Dr Byte House', 107 | type: 'HealthCheck', 108 | description: 'Digital overdose, very bytestatic. Otherwise healthy.', 109 | healthCheckRating: 0, 110 | }, 111 | ], 112 | }, 113 | ]; 114 | 115 | export default patients; 116 | -------------------------------------------------------------------------------- /blogs_for_test.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const blogs = [ 3 | { 4 | _id: "5a422a851b54a676234d17f7", 5 | title: "React patterns", 6 | author: "Michael Chan", 7 | url: "https://reactpatterns.com/", 8 | likes: 7, 9 | __v: 0 10 | }, 11 | { 12 | _id: "5a422aa71b54a676234d17f8", 13 | title: "Go To Statement Considered Harmful", 14 | author: "Edsger W. Dijkstra", 15 | url: "http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html", 16 | likes: 5, 17 | __v: 0 18 | }, 19 | { 20 | _id: "5a422b3a1b54a676234d17f9", 21 | title: "Canonical string reduction", 22 | author: "Edsger W. Dijkstra", 23 | url: "http://www.cs.utexas.edu/~EWD/transcriptions/EWD08xx/EWD808.html", 24 | likes: 12, 25 | __v: 0 26 | }, 27 | { 28 | _id: "5a422b891b54a676234d17fa", 29 | title: "First class tests", 30 | author: "Robert C. Martin", 31 | url: "http://blog.cleancoder.com/uncle-bob/2017/05/05/TestDefinitions.htmll", 32 | likes: 10, 33 | __v: 0 34 | }, 35 | { 36 | _id: "5a422ba71b54a676234d17fb", 37 | title: "TDD harms architecture", 38 | author: "Robert C. Martin", 39 | url: "http://blog.cleancoder.com/uncle-bob/2017/03/03/TDD-Harms-Architecture.html", 40 | likes: 0, 41 | __v: 0 42 | }, 43 | { 44 | _id: "5a422bc61b54a676234d17fc", 45 | title: "Type wars", 46 | author: "Robert C. Martin", 47 | url: "http://blog.cleancoder.com/uncle-bob/2016/05/01/TypeWars.html", 48 | likes: 2, 49 | __v: 0 50 | } 51 | ] 52 | ``` 53 | -------------------------------------------------------------------------------- /contributors.md: -------------------------------------------------------------------------------- 1 | Numerous people have improved and corrected the material both in content and spelling. Big thanks to everybody! 2 | 3 | ### Full stack open 2020 4 | 5 | https://github.com/fullstack-hy2020/fullstack-hy2020.github.io/graphs/contributors 6 | 7 | ### Full stack open 2019 8 | 9 | https://github.com/fullstackopen-2019/fullstackopen-2019.github.io/graphs/contributors 10 | 11 | ### Full stack -websovelluskehitys 2019 12 | 13 | https://github.com/fullstack-hy2019/fullstack-hy2019.github.io/graphs/contributors 14 | 15 | ### Full stack open 2018 16 | 17 | https://github.com/fullstackopen/fullstackopen.github.io/graphs/contributors 18 | 19 | ### Full stack -websovelluskehitys 2018 20 | 21 | https://github.com/FullStack-HY/FullStack-Hy.github.io/graphs/contributors 22 | -------------------------------------------------------------------------------- /diagnoses.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "code": "M24.2", 4 | "name": "Disorder of ligament", 5 | "latin": "Morbositas ligamenti" 6 | }, 7 | { 8 | "code": "M51.2", 9 | "name": "Other specified intervertebral disc displacement", 10 | "latin": "Alia dislocatio disci intervertebralis specificata" 11 | }, 12 | { 13 | "code": "S03.5", 14 | "name": 15 | "Sprain and strain of joints and ligaments of other and unspecified parts of head", 16 | "latin": 17 | "Distorsio et/sive distensio articulationum et/sive ligamentorum partium aliarum sive non specificatarum capitis" 18 | }, 19 | { 20 | "code": "J10.1", 21 | "name": 22 | "Influenza with other respiratory manifestations, other influenza virus codeentified", 23 | "latin": 24 | "Influenza cum aliis manifestationibus respiratoriis ab agente virali codeentificato" 25 | }, 26 | { 27 | "code": "J06.9", 28 | "name": "Acute upper respiratory infection, unspecified", 29 | "latin": "Infectio acuta respiratoria superior non specificata" 30 | }, 31 | { 32 | "code": "Z57.1", 33 | "name": "Occupational exposure to radiation" 34 | }, 35 | { 36 | "code": "N30.0", 37 | "name": "Acute cystitis", 38 | "latin": "Cystitis acuta" 39 | }, 40 | { 41 | "code": "H54.7", 42 | "name": "Unspecified visual loss", 43 | "latin": "Amblyopia NAS" 44 | }, 45 | { 46 | "code": "J03.0", 47 | "name": "Streptococcal tonsillitis", 48 | "latin": "Tonsillitis (palatina) streptococcica" 49 | }, 50 | { 51 | "code": "L60.1", 52 | "name": "Onycholysis", 53 | "latin": "Onycholysis" 54 | }, 55 | { 56 | "code": "Z74.3", 57 | "name": "Need for continuous supervision" 58 | }, 59 | { 60 | "code": "L20", 61 | "name": "Atopic dermatitis", 62 | "latin": "Atopic dermatitis" 63 | }, 64 | { 65 | "code": "F43.2", 66 | "name": "Adjustment disorders", 67 | "latin": "Perturbationes adaptationis" 68 | }, 69 | { 70 | "code": "S62.5", 71 | "name": "Fracture of thumb", 72 | "latin": "Fractura [ossis/ossium] pollicis" 73 | }, 74 | { 75 | "code": "H35.29", 76 | "name": "Other proliferative retinopathy", 77 | "latin": "Alia retinopathia proliferativa" 78 | } 79 | ] 80 | -------------------------------------------------------------------------------- /diagnoses.ts: -------------------------------------------------------------------------------- 1 | const data = [ 2 | { 3 | "code": "M24.2", 4 | "name": "Disorder of ligament", 5 | "latin": "Morbositas ligamenti" 6 | }, 7 | { 8 | "code": "M51.2", 9 | "name": "Other specified intervertebral disc displacement", 10 | "latin": "Alia dislocatio disci intervertebralis specificata" 11 | }, 12 | { 13 | "code": "S03.5", 14 | "name": 15 | "Sprain and strain of joints and ligaments of other and unspecified parts of head", 16 | "latin": 17 | "Distorsio et/sive distensio articulationum et/sive ligamentorum partium aliarum sive non specificatarum capitis" 18 | }, 19 | { 20 | "code": "J10.1", 21 | "name": 22 | "Influenza with other respiratory manifestations, other influenza virus codeentified", 23 | "latin": 24 | "Influenza cum aliis manifestationibus respiratoriis ab agente virali codeentificato" 25 | }, 26 | { 27 | "code": "J06.9", 28 | "name": "Acute upper respiratory infection, unspecified", 29 | "latin": "Infectio acuta respiratoria superior non specificata" 30 | }, 31 | { 32 | "code": "Z57.1", 33 | "name": "Occupational exposure to radiation" 34 | }, 35 | { 36 | "code": "N30.0", 37 | "name": "Acute cystitis", 38 | "latin": "Cystitis acuta" 39 | }, 40 | { 41 | "code": "H54.7", 42 | "name": "Unspecified visual loss", 43 | "latin": "Amblyopia NAS" 44 | }, 45 | { 46 | "code": "J03.0", 47 | "name": "Streptococcal tonsillitis", 48 | "latin": "Tonsillitis (palatina) streptococcica" 49 | }, 50 | { 51 | "code": "L60.1", 52 | "name": "Onycholysis", 53 | "latin": "Onycholysis" 54 | }, 55 | { 56 | "code": "Z74.3", 57 | "name": "Need for continuous supervision" 58 | }, 59 | { 60 | "code": "L20", 61 | "name": "Atopic dermatitis", 62 | "latin": "Atopic dermatitis" 63 | }, 64 | { 65 | "code": "F43.2", 66 | "name": "Adjustment disorders", 67 | "latin": "Perturbationes adaptationis" 68 | }, 69 | { 70 | "code": "S62.5", 71 | "name": "Fracture of thumb", 72 | "latin": "Fractura [ossis/ossium] pollicis" 73 | }, 74 | { 75 | "code": "H35.29", 76 | "name": "Other proliferative retinopathy", 77 | "latin": "Alia retinopathia proliferativa" 78 | } 79 | ]; 80 | 81 | export default data; 82 | -------------------------------------------------------------------------------- /diaryentries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "date": "2017-01-01", 5 | "weather": "rainy", 6 | "visibility": "poor", 7 | "comment": "Pretty scary flight, I'm glad I'm alive" 8 | }, 9 | { 10 | "id": 2, 11 | "date": "2017-04-01", 12 | "weather": "sunny", 13 | "visibility": "good", 14 | "comment": "Everything went better than expected, I'm learning much" 15 | }, 16 | { 17 | "id": 3, 18 | "date": "2017-04-15", 19 | "weather": "windy", 20 | "visibility": "good", 21 | "comment": "I'm getting pretty confident although I hit a flock of birds" 22 | }, 23 | { 24 | "id": 4, 25 | "date": "2017-05-11", 26 | "weather": "cloudy", 27 | "visibility": "good", 28 | "comment": "I almost failed the landing but I survived" 29 | } 30 | ] -------------------------------------------------------------------------------- /dokumenttitietokannat.MD: -------------------------------------------------------------------------------- 1 | # Dokumenttitietokannat, esim. MongoDB 2 | 3 | Dokumenttitietokantojen voi ajatella sijoittuvan jonnekin relaatiotietokantojen ja avain-arvotietokantojen puolen välin tienoille. Dokumenttikannat perustuvat avain-arvotietokantojen tapaan arvojen tallettamiseen avaimen perusteella. Arvot tai *dokumentit* kuten niitä dokumenttikantojen kontekstissa nimitetään voivat kuitenkin olla itsessään hyvin monimutkaisia oliota, jotka sisältävät kenttiä, joiden arvona voi olla joko normaaleja arvoja kuten lukuja ja merkkijonoja tai muita olioita. Toisin kuin avain-arvotietokannoissa, dokumenttikannat "näkevät" tietokantaan talletettujen dokumenttien sisään, ja mahdollistavat talletettujen dokumenttien sisällön suhteen tehdyt kyselyt. 4 | 5 | Käytetään seuraavassa esimerkkinä ylivoimaisesti suosituimman dokumenttitietokannan MongoDB:n merkintöjä. 6 | 7 |
8 | 9 | Dokumenttikannoissa käytetään tiedon loogisena esitysmuotona yleensä JSON:ia. Seuraavassa kurssia *Ohjelmoinnin perusteet* esittävä JSON-dokumentti: 10 | 11 | 12 | ```javascript 13 | { 14 | "id": ObjectId("10"), 15 | "nimi": "Ohjelmistotekniikka", 16 | "laajuus": 5, 17 | "luennot": [ "Matti Luukkainen" ] 18 | } 19 | ``` 20 | 21 | JSON-dokumentti koostuu avain-arvo-pareista. Avainta vastaava arvo merkitään kaksoispisteellä erotettuna avaimen yhteyteen. 22 | 23 | Kurssi-dokumentissa on siis neljä avain-arvo-paria. Voidaankin ajatella että kurssilla on neljä kenttää. Näistä kentistä erikoisasemassa on MongoDB:n dokumentille automaattisesti generoima avainkenttä `id` jonka arvo on tyypiltään `ObjectId`. Poikkeavaa relaatiotietokantoihin nähden on se, että kentän arvona voi olla taulukko. 24 | 25 | Seuraavassa on opiskelijaa kuvaava dokumentti: 26 | 27 | 28 | ```javascript 29 | { 30 | "id" : ObjectId("59"), 31 | "nimi" : "Pekka Mikkola", 32 | "opiskelijanumero" : 14112345, 33 | "osoite" : { 34 | "katu" : "Tehtaankatu 10 B 1", 35 | "postinumero" : "00120", 36 | "postitoimipaikka" : "Helsinki" 37 | } 38 | } 39 | ``` 40 | 41 | Nyt kentän osoite arvona on *olio*, jolla on itsellään omat kenttänsä. 42 | 43 | Dokumenttitietokannassa dokumentit on lajiteltu *kokoelmiin* (engl. collection). Kokoelman merkitys on suunnilleen sama kuin taulun relaatiotietokannassa. Yhdessä kokoelmassa olevien dokumenttien ei kuitenkaan tarvitse olla kentiltään samanlaisia. Kenttiä voi olla vaihteleva määrä ja saman nimiset kentät voivat sisältää eri dokumenteilla eri tyyppisen arvon. Kokoelmille ei määritellä dokumenttikannoissa minkäänlaista skeemaa, eli on täysin sovellusten vastuulla, että kantaan talletetaan järkevää dataa, ja että kannasta luettava data tutkitaan oikein. 44 | 45 | Kuten edellä opiskelijan kohdalla näimme, on dokumenttikannoissa mahdollista sisällyttää olioita toistensa sisään. Tilanne olisi myös voitu mallintaa "relaatiomallin tapaan" siten, että osoitteita varten olisi oma kokoelmansa, ja yksittäinen osoite mallinnettaisiin omana dokumenttina: 46 | 47 | 48 | ```javascript 49 | { 50 | "id" : ObjectId("123"), 51 | "katu" : "Tehtaankatu 10 B 1", 52 | "postinumero" : "00120", 53 | "postitoimipaikka" : "Helsinki" 54 | } 55 | ``` 56 | 57 | Opiskelijadokumentti sisältäisi nyt ainoastaan viitteen osoitedokumenttiin: 58 | 59 | 60 | ```javascript 61 | { 62 | "id" : ObjectId("59"), 63 | "nimi" : "Pekka Mikkola", 64 | "opiskelijanumero" : 14112345, 65 | "osoite" : ObjectId("123") 66 | } 67 | ``` 68 | 69 | Toisin kuin relaatiotietokantojen tapauksessa, dokumenttikannat *eivät tarjoa* tietokannan tasolla tapahtuvia *liitosoperaatiota*, ja edellisen esimerkin tapauksessa sovelluksen olisi itse huolehdittava siitä, että opiskelijaa haettaessa haetaan myös opiskelijan osoite tietokannasta. 70 | 71 | Vaikka operaatio ei olekaan dokumenttikannan tasolla tuettu, on olemassa monia kirjastoja, jotka toteuttavat ohjelmallisen liitosoperaation siten, että sovellusohjelman ei tarvitse siitä huolehtia. 72 | 73 | Relaatiotietokannoissa kannan skeeman muodostaminen on sikäli helppoa, että normalisoituun ratkaisuun pyrittäessä useimmissa tilanteissa on olemassa noin yksi "järkevä" ratkaisu, joka toimii lähes yhtä hyvin riippumatta siitä miten kantaa käytetään. 74 | 75 | Dokumenttikantojen suhteen tilanne on toinen. Tarkastellaan esimerkiksi Kursseja ja Opiskelijoiden kurssisuorituksia. Relaatiotietokannassa tilanne olisi suoraviivainen, *Suoritus* olisi *Kurssin* ja *Opiskelijan* liitostaulu. 76 | 77 | Eräs mahdollisuus olisi tehdä täsmälleen sama ratkaisu dokumenttikannassa. 78 | 79 | Kokoelma Opiskelija: 80 | 81 | ```javascript 82 | [ 83 | { 84 | "id": ObjectId("10"), 85 | "nimi" : "Lea Kutvonen", 86 | "opiskelijanumero" : 13457678 87 | }, 88 | { 89 | "id": ObjectId("11"), 90 | "nimi" : "Pekka Mikkola", 91 | "opiskelijanumero" : 14012345 92 | } 93 | ] 94 | ``` 95 | 96 | Kokoelma kurssi: 97 | 98 | ```javascript 99 | [ 100 | { 101 | "id": ObjectId("34"), 102 | "nimi" : "Ohjelmoinnin perusteet", 103 | "laajuus" : 5 104 | }, 105 | { 106 | "id": ObjectId("35"), 107 | "nimi" : "Tietokone työvälineenä", 108 | "laajuus" : 1 109 | } 110 | ] 111 | ``` 112 | 113 | Suoritus olisi nyt "liitostaulumainen" kokoelma: 114 | 115 | 116 | ```javascript 117 | [ 118 | { 119 | "id": 55 120 | "kurssi_id" : ObjectId("34"), 121 | "opiskelija_id" : ObjectId("10"), 122 | "arvosana" : 4 123 | }, 124 | { 125 | "id": 56 126 | "kurssi_id" : ObjectId("35"), 127 | "opiskelija_id" : ObjectId("10"), 128 | "arvosana" : 5 129 | }, 130 | { 131 | "id": 57 132 | "kurssi_id" : ObjectId("35"), 133 | "opiskelija_id" : ObjectId("11"), 134 | "arvosana" : 2 135 | } 136 | ] 137 | ``` 138 | 139 | Vaihtoehtoja on kuitenkin myös muita. Käyttötapauksista riippuen saattaisi olla edullista tallettaa tieto suorituksista ("liitosdokumentin" id) myös kurssin ja opiskelijan yhteyteen: 140 | 141 | Kokoelma Opiskelija: 142 | 143 | ```javascript 144 | [ 145 | { 146 | "id": ObjectId("10") 147 | "nimi" : "Lea Kutvonen", 148 | "opiskelijanumero" : 13457678, 149 | "suoritukset" : [ ObjectId("55"), ObjectId("56") ] 150 | }, 151 | { 152 | "id": ObjectId("11") 153 | "nimi" : "Pekka Mikkola", 154 | "opiskelijanumero" : 14012345, 155 | "suoritukset" : [ ObjectId("57") ] 156 | } 157 | ] 158 | ``` 159 | 160 | Kokoelma kurssi: 161 | 162 | ```javascript 163 | [ 164 | { 165 | "id": ObjectId("34") 166 | "nimi" : "Ohjelmoinnin perusteet", 167 | "laajuus" : 5, 168 | "suorittajat" : [ObjectId("10")] 169 | }, 170 | { 171 | "id": ObjectId("35") 172 | "nimi" : "Tietokone työvälineenä", 173 | "laajuus" : 1, 174 | "suorittajat" : [ObjectId("10"), ObjectId("11")] 175 | } 176 | ] 177 | ``` 178 | 179 | Jossain tapauksessa paras ratkaisu olisi luopua liitoksena toimivista dokumenteista eli kokoelmasta suoritukset ja tallettaa suoritukset kokonaisuudessaan opiskelija-dokumentteihin: 180 | 181 | ```javascript 182 | [ 183 | { 184 | "id": ObjectId("10") 185 | "nimi" : "Lea Kutvonen", 186 | "opiskelijanumero" : 13457678, 187 | "suoritukset" : [ 188 | { 189 | "id": 55 190 | "kurssi_id" : ObjectId("34"), 191 | "arvosana" : 4 192 | }, 193 | { 194 | "id": 56 195 | "kurssi_id" : ObjectId("35"), 196 | "arvosana" : 5 197 | } 198 | ] 199 | }, 200 | { 201 | "id": ObjectId("11") 202 | "nimi" : "Pekka Mikkola", 203 | "opiskelijanumero" : 14012345, 204 | "suoritukset" : [ 205 | { 206 | "id": 57 207 | "kurssi_id" : ObjectId("35"), 208 | "arvosana" : 2 209 | } 210 | ] 211 | } 212 | ] 213 | ``` 214 | 215 | Tämä ratkaisu vaikeuttaisi kurssin suorittajien selvittämistä, joten joissain käyttötapauksissa saattaisi olla edullista sisällyttää suoritukset *molempiin* opiskelijoihin ja kurssiin. 216 | 217 | Yhtä "oikeaa" vastausta miten sovelluksen data kannattaa mallintaa dokumenttikannan kokoelmiksi ja dokumenteiksi ei ole olemassa. Parhaaseen tapaan vaikuttaa suuresti se minkälainen käyttöprofiili rakennettavalla sovelluksella on: datamalli kannattaa valita siten, että se tekee yleisimpien operaatioiden suorituksen nopeaksi ja helpoksi. 218 | 219 | Kuten jo totesimme, dokumenttikannat eivät tue liitosoperaatioita, ja kyselyt kohdistuvat aina vain yhteen kokoelmaan. Dokumenttikannoilla ei ole mitään standardoitua kyselykieltä, jokaisen kannan kyselykieli on täysin omanlaisensa. Esim. MongoDB:n kyselykieli ei muistuta kovinkaan läheisesti SQLää. 220 | 221 | Dokumenttikannat eivät myöskään tue useamman kokoelman yhtäaikaista muuttamista transaktionaalisesti. Kaikki yhteen kokoelmaan suoritettavat tapahtumat tehdään kuitenkin aina transaktionaalisesti. -------------------------------------------------------------------------------- /fly_io_problem.md: -------------------------------------------------------------------------------- 1 | ## Problems connecting to Fly.io 2 | 3 | In some cases (the cause is so far unknown) running Fly.io especially on Windows WSL has caused problems. If the following command just hangs 4 | 5 | ```bash 6 | flyctl ping -o personal 7 | ``` 8 | 9 | your computer can not for some reason connect to Fly.io 10 | 11 | If you run to this problem, you might try [GitHub Actions](https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/), (that is the topic of the [part 11](https://fullstackopen.com/en/part11) of this course). With GitHub actions you can ask GitHub to deploy the app onto Fly.io. 12 | 13 | The steps to setup GitHub Actions for your project are as follows: 14 | 15 | - Delete your current (non-functioning) apps on fly.io in your dashboard https://fly.io/dashboard 16 | - Create a secret key in fly at https://fly.io/user/personal_access_tokens and hold on to it 17 | - Go to your github repo `https://github.com///settings` , head to 'Secrets ' => 'Actions', and then create a repo secret key `FLY_API_TOKEN` with the secret key in the previous step 18 | - Reclone your repo so that you have an updated repo locally (you can delete the old files before recloning) 19 | - Edit .gitignore and remove `fly.toml`. This file will need to be pushed into the repository to allow deployment to happen 20 | - `fly auth login` in your terminal to make sure you're authenticated, then run `fly launch` to create new app in your root directory, but choose **not** to deploy - this will generate a new `fly.toml` file - double check this file has `app = ""` to make sure it's pointing to the correctly named app seen on your dashboard 21 | - Create a new directory tree from the root directory leading to a main.yml file - .github/workflows/main.yml with these contents: 22 | 23 | ```yml 24 | name: Fly Deploy 25 | on: [push] 26 | env: 27 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 28 | jobs: 29 | deploy: 30 | name: Deploy app 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v2 34 | - uses: superfly/flyctl-actions/setup-flyctl@master 35 | - run: flyctl deploy --remote-only 36 | ``` 37 | 38 | .yml file is used by GitHub Actions to run each command and deploy your app onto the fly server, and Actions will use the API token we created in step (3) 39 | 40 | - Commit your now-updated repo with all these changes (`git add .`, `git commit -m ""`, `git push`) 41 | - You can head to `https://github.com///actions` and see github Actions in-progress; your app will be deploy if everything checks out 42 | - Now whenever there is a new commit, Github Actions will redeploy your app automatically! You can read more about this github feature [here](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#create-an-example-workflow). 43 | - If on future commits the react app on Fly.io shows a blank screen with an error 404 for a `.js` build file, try to `ctrl-F5` / clean-reload the tab to clear cache - if you think fly.io app isn't up to date, you can rerun a Github Action: head to the Actions tab, click on the most recent workflow runs, then the button _rerun all jobs_. 44 | 45 | Big thanks [Leon Poon](https://github.com/Sheceido) for writing this! 46 | -------------------------------------------------------------------------------- /harjoitustyo-hy.md: -------------------------------------------------------------------------------- 1 | # Full Stack -websovelluskehitys harjoitustyö 1-10 op 2 | 3 | > Tätä harjoitustyötä *ei lasketa* aineopintojen harjoitustyöksi, joita vaaditaan tietojenkäsittelytieteen pääaineopiskelijoilta 2 kappaletta, eli et voi korvata Full Stack -harjoitustyöllä esim. tiralabraa. 4 | 5 | Harjoitustyössä toteutetaan vapaavalintainen sovellus Reactilla ja/tai Nodella. Myös React Nativella toteutettu mobiilisovellus on mahdollinen, jolloin suositeltu kehitysalusta on [Expo](https://expo.io/). 6 | 7 | Sovelluksen backendia ei siis ole pakko toteuttaa Nodella. Backendia ei myöskään tarvitse toteuttaa itse, valmiit rajapinnat tai palvelut kuten Firebase kelpaavat. Frontend on mahdollista tehdä myös jollain muulla järkevällä tavalla kuin Reactilla, esim. Vue.js:llä. Tällöin olisi hyvä jos sovelluksella olisi Nodella tehty backend. 8 | 9 | Opintopistemäärä määrittyy käytettyjen työtuntien mukaan, yksi opintopiste vastaa 17.5 tuntia. Työ arvostellaan skaalalla hyväksytty/hylätty. Hylkäämiseen voi johtaa ainoastaa se, että et ole itse tehnyt työtäsi tai jos tuntikirjanpito ja GitHub-repositorion commit-historia eivät vastaa toisiaan. Työtunteihin lasketaan kaikki sovelluksen tekemiseen käytetty aika, eli vaikka tekisit backendin jollain muulla tekniikalla kuin Nodella, lasketaan se silti työaikaan. 10 | 11 | Työtunteihin _ei lasketa_ kurssin asioiden kertaamiseen käytettyä aikaa. 12 | 13 | Harjoitustyö on mahdollista tehdä myös pari- tai ryhmätyönä. 14 | 15 | Full Stack -kurssin Telegram-kanava https://t.me/fullstackcourse toimii myös harjoitustyön kanavana. 16 | 17 | ## Ilmoittautuminen ja aiheen hyväksyminen 18 | 19 | Jos käytät harjoitustyössä kurssilta tuttuja tekniikoita, ei aiheen hyväksymistä tarvita. Jos käytät jotain muita tekniikoita, varmista, että aihe sopii lähettämällä emailia osoitteeseen matti.luukkainen@helsinki.fi 20 | 21 | Jos et ole Helsingin yliopiston tutkinto-opiskelija, ilmoittaudu kurssille [avoimen yliopiston sivuilla](https://courses.helsinki.fi/en/aytkt21010/133171831) viimeistään siinä vaiheessa kun palautat työn. HY:n opinto-oikeuden omaavien ei tarvitse ilmoittautua kurssille. 22 | 23 | ## Vaatimukset palautettavalle työlle 24 | 25 | GitHub-repositoriosi README.md-tiedostossa tulee olla linkki osoitteeseen, missä sovellus on käynnissä. Expolla toteutetun React Native mobiilisovelluksen kohdalla repositoriossa tulee olla [julkinen linkki](https://docs.expo.io/versions/latest/workflow/publishing/#how-to-publish), jonka kautta sovelluksen voi käynnistää Expon mobiilisovelluksessa. Muissa tapauksissa sovitaan sovelluksen demoamisesta tapauskohtaisesti. 26 | 27 | README:stä täytyy myös löytyä linkki sovelluksen käyttöohjeisiin sekä työaikakirjanpitoon. Sopiva tarkkuustaso työaikakirjanpidolle on [tämä](https://github.com/mluukkai/OtmTodoApp/blob/master/dokumentaatio/tuntikirjanpito.md). Työaikakirjanpidosta **on myös selvittävä työtuntien yhteenlaskettu määrä**. 28 | 29 | Työaikakirjanpidon ja repositorion commit-historian tulee vastata toisiaan kohtuullisessa määrin. Tee siis välicommiteja! 30 | 31 | Pari- tai ryhmätöissä jokaisen on pidettävä omaa työaikakirjanpitoa. 32 | 33 | Ilman työaikakirjanpitoa kurssin maksimiopintopistemäärä on 1 op. 34 | 35 | ## Työn valmistuminen ja arvostelu 36 | 37 | Kun työsi on valmis arvosteltavaksi, lähetä email osoitteeseen matti.luukkainen@helsinki.fi 38 | 39 | Harjoitustyöt tulee palauttaa 14.3.2021 klo 23:59 mennessä. 40 | -------------------------------------------------------------------------------- /harjoitustyo.md: -------------------------------------------------------------------------------- 1 | # Full Stack -websovelluskehitys harjoitustyö 5, 7 tai 10 op 2 | 3 | Harjoitustyössä toteutetaan vapaavalintainen sovellus Reactilla ja/tai Nodella. Myös React Nativella toteutettu mobiilisovellus on mahdollinen. 4 | 5 | Sovelluksen backendia ei siis ole pakko toteuttaa Nodella. Backendia ei myöskään tarvitse toteuttaa itse, valmiit rajapinnat tai palvelut kuten Firebase kelpaavat. Frontend on mahdollista tehdä myös jollain muulla järkevällä tavalla kuin Reactilla, esim. Vue.js:llä. Tällöin olisi hyvä jos sovelluksella olisi Nodella tehty backend. 6 | 7 | Opintopistemäärä määrittyy käytettyjen työtuntien mukaan, yksi opintopiste vastaa 17.5 tuntia. 5 opintopisteen suoritukseen tarvitaan vähintään 87.5 tunnin työskentely, 7 opintopisteen suoritukseen 122.5 tunnin työskentely ja 10 opintopisteen suoritus edellyttää vähintään 175 työtuntia. Jokaista opintopistettä kohti on laskettu myös tapahtuvan 9.5 tuntia omatoimista harjoitustyössä käytettävien tekniikoiden opiskelua. 8 | 9 | Työ arvostellaan skaalalla hyväksytty/hylätty. 10 | 11 | Työsi voidaan hylätä suoraan mikäli et ole itse tehnyt työtäsi tai jos tuntikirjanpito ja GitHub-repositorion commit-historia eivät vastaa kohtuullisesti toisiaan. Työtunteihin lasketaan kaikki sovelluksen tekemiseen käytetty aika, eli vaikka tekisit backendin jollain muulla tekniikalla kuin Nodella, lasketaan se silti työaikaan. 12 | 13 | Työtunteihin _ei lasketa_ kurssin asioiden kertaamiseen käytettyä aikaa. 14 | 15 | **Hyväksytty suoritus** edellyttää, että käytetyt tunnit näkyvät myös sovelluksesi toiminnallisuuksissa ja ominaisuuksissa. 16 | Esimerkiksi 10 opintopisteen työhön edellytetään useampaa kuin yhtä tai kahta toiminnallisuutta. Hyvä lähtökohta on esim. se, että sovelluksesi toteuttaa monipuolisia tietokantatoimintoja, eli CRUD-toiminnot (_create, read, update_ ja _delete_). Vaihtoehtoisesti sovelluksesi toteuttaa osaa (mutta ei vain yhtä) näistä monipuolisesti. Testaus ja esimerkiksi deployauksen automatisointi ja muu huomattava konfigurointi luonnollisesti lasketaan laajuuteen. 17 | 18 | Mikäli sovelluksesi sisältää hyvin rajallisesti toimintoja, voidaan etenkin 7 ja 10 opintopisteen suorituksiin sinua pyytää täydentämään sovellustasi. 19 | 20 | **Huomaa:** Ei riitä, että tekemäsi toiminnallisuus toimii vain omalla koneella, vaan samat ominaisuudet tulisi olla testattavissa myös osoitteessa, jossa sovelluksesi on käynnissä. 21 | 22 | Harjoitustyö on mahdollista tehdä myös pari- tai ryhmätyönä. 23 | 24 | Full Stack -kurssin Discord-kanava https://study.cs.helsinki.fi/discord/join/fullstack toimii myös harjoitustyön kanavana. 25 | 26 | ## Ilmoittautuminen ja aiheen hyväksyminen 27 | 28 | Jos käytät harjoitustyössä kurssilta tuttuja tekniikoita, ei aiheen hyväksymistä tarvita. Jos käytät jotain muita tekniikoita, varmista, että aihe sopii lähettämällä emailia osoitteeseen matti.luukkainen@helsinki.fi 29 | 30 | ## Ohjeet video-tutoriaalien käyttämiseen 31 | 32 | Video-tutoriaalit voivat olla hyödyllinen tapa oppia uusia taitoja ja ymmärtää konsepteja, erityisesti kun aloitat uuden teknologian tai kehyksen käytön. Kuitenkin full stack -kurssin projektissa on tärkeää osoittaa oma ymmärryksesi ja kurssilla oppimasi taidot. Varmistaaksesi, että projektisi heijastaa henkilökohtaisia taitojasi ja ponnistelujasi, noudata näitä ohjeita käyttäessäsi video-oppaita referenssinä: 33 | 34 | ### 1. Käytä tutoriaaleja oppimiseen, Ei suoraan kopiointiin 35 | - **Tarkoitus:** Video-tutoriaalit tulisi nähdä oppimateriaaleina, jotka auttavat ymmärtämään konsepteja, eikä suoraan kopioitavina malleina. 36 | - **Odotus:** Opetusvideon katsomisen jälkeen sovella oppimaasi tietoa rakentaaksesi jotain ainutlaatuista tai laajentaaksesi videon esimerkkisovellusta omilla ominaisuuksillasi. 37 | 38 | ### 2. Tee merkittäviä muutoksia 39 | - **Omaperäisyys:** Projektisi tulisi erota merkittävästi opetusvideosta. Tämä voi sisältää muutoksia käyttöliittymään, lisättyjä ominaisuuksia, erilaista toiminnallisuutta tai eri teknologiapinon käyttöä. 40 | - **Muutosesimerkkejä:** 41 | - Uusien ominaisuuksien lisääminen, joita ei käsitellä videossa (esim. käyttäjäprofiilit, ilmoitukset, hakutoiminnallisuus). 42 | - Erilaisten tai kehittyneempien tekniikoiden toteuttaminen (esim. API-integraatio, eri tietokannan käyttö). 43 | - Sovelluksen tyylin muuttaminen tai vaihtoehtoisen frontend-kehyksen käyttö. 44 | 45 | ### 3. Lisää uniikkeja ominaisuuksia 46 | - **Mukauttaminen:** Lisää ominaisuuksia, joita ei ollut mukana opetusvideossa, esitelläksesi taitojasi. Nämä voivat olla kurssin tavoitteisiin liittyviä ominaisuuksia tai sellaisia, jotka parantavat käyttäjäkokemusta. 47 | - **Esimerkkejä ainutlaatuisista ominaisuuksista:** 48 | - Sosiaalisen median sovelluksessa: Ryhmien lisääminen, chat-toiminnallisuus tai kuvan latausominaisuus. 49 | - Verkkokauppa-sovelluksessa: Toivelista, arvostelut tai maksuyhdyskäytävä. 50 | 51 | ### 4. Selitä erot selkeästi 52 | - **Dokumentointi:** Projektin palautuksen yhteydessä liitä mukaan lyhyt dokumentti, jossa selität, mitkä osat sovelluksestasi olivat inspiroituneet opetusvideosta ja mitä muutoksia tai parannuksia teit. 53 | - **Perustelu:** Kerro, miksi valitsit tehdä nämä muutokset ja kuinka ne parantavat tai erottavat projektiasi alkuperäisestä. 54 | 55 | ### 5. Mene perusasioiden yli 56 | - **Kehittyneet ominaisuudet:** Osoittaaksesi ymmärryksesi, harkitse kehittyneiden ominaisuuksien lisäämistä, joita ei käsitelty opetusvideossa, kuten: 57 | - **CI/CD pipelinet:** Jatkuvan integraation ja käyttöönoton määrittäminen sovellukselle. 58 | - **Testaus:** Yksikkötestien, integraatiotestien tai end-to-end -testien toteuttaminen. 59 | - **Validointi:** Käyttäjätunnistus, datan validointi ja herkän tiedon turvallinen käsittely. 60 | 61 | ### 6. Vältä plagiointia 62 | - **Oma työ:** Koodin kopioiminen suoraan opetusvideosta ilman muutoksia katsotaan plagioinniksi. Projektin tulee heijastaa omia ponnistelujasi ja ymmärrystäsi. 63 | - **Lähteen mainitseminen:** Jos käytät tiettyjä koodiesimerkkejä tai ideoita videosta, varmista, että annat asianmukaisen kunnianlauseen koodikommenteissa tai dokumentaatiossa. 64 | 65 | Noudattamalla näitä ohjeita voit käyttää video-oppaita tehokkaasti oppimisvälineenä varmistaen samalla, että projektisi heijastaa omaa luovuuttasi ja taitojasi. 66 | 67 | 68 | ## Vaatimukset palautettavalle työlle 69 | 70 | - Projektirepositoriosi README-tiedosta **tulee löytyä** seuraavat asiat: 71 | - Linkki osoitteeseen, jossa sovelluksesi on käynnissä. Expolla toteutetun React Native mobiilisovelluksen kohdalla repositoriossa tulee olla [julkinen linkki](https://docs.expo.io/versions/latest/workflow/publishing/#how-to-publish), jonka kautta sovelluksen voi käynnistää Expon mobiilisovelluksessa. Muissa tapauksissa sovitaan sovelluksen demoamisesta tapauskohtaisesti 72 | - Lyhyt kuvaus sovelluksestasi (mitä se tekee?) sekä jonkinlaiset käyttöohjeet (tai linkki niihin) 73 | - Linkki tuntikirjanpitoon. Kirjanpito oltava "heti nähtävässä" -muodossa, eli ei erikseen ladattavana excelinä. Käytä esim. markdownia (.md) 74 | - Riittävä kirjanpidon tarkkuus on suunnilleen [tämä](https://github.com/mluukkai/OtmTodoApp/blob/master/dokumentaatio/tuntikirjanpito.md) 75 | - Työaikakirjanpidosta **on myös selvittävä työtuntien yhteenlaskettu määrä** 76 | - Työaikakirjanpidon ja repositorion commit-historian tulee vastata toisiaan kohtuullisessa määrin. Tee siis välicommiteja! 77 | - Pari- tai ryhmätöissä jokaisen on pidettävä omaa työaikakirjanpitoa 78 | - Jos työsi on useassa repositoriossa, linkit muihin repositorioihin 79 | 80 | - Repositoriosi ja koodikantasi on oltava edustuskelpoisessa tilassa. Ei poiskommentoitua tai muuten käyttämätöntä koodia. 81 | 82 | Jos ylläolevat edellytykset eivät täyty pyytäessäsi kurssisuoritusta, sinua tullaan joka tapauksessa pyytämään täydentämään ne ennen tarkastuksen etenemistä. 83 | 84 | Miksi? 85 | 86 | Repositoriosi on käyntikorttisi, eritoten alan työnhaussa. Rekrytoijat eivät arvosta, mikäli joutuvat penkomaan sotkuista repoa saadakseen kuvan osaamisestasi ja luomuksestasi. (Eivät myöskään projektin tarkastajat.) Monet rekrytoijat eivät edes vaivaudu vaan siirtyvät eteenpäin. On suotavaa harjoittaa hyviä käytänteitä aina kun mahdollista. 87 | 88 | ## Työn valmistuminen ja arvostelu 89 | 90 | Kun työsi on valmis arvosteltavaksi, **tee KAIKKI seuraavista askeleista** 91 | 92 | (1) Ilmoittaudu avoimen yliopiston kautta oikean kokoiseen kurssiin : 93 | - FS harjoitustyö (5 op): [https://www.avoin.helsinki.fi/palvelut/esittely.aspx?s=otm-213e3838-c757-4839-bdb9-69dc7e23571c](https://www.avoin.helsinki.fi/palvelut/esittely.aspx?s=otm-7b7d5cc3-2bb5-435d-8c33-752a51238f00) 94 | - FS harjoitustyö (7 op): https://www.avoin.helsinki.fi/palvelut/esittely.aspx?s=otm-5b229b06-d94e-483d-becf-e50c0f30ac47 95 | - FS harjoitustyö (10 op): [https://www.avoin.helsinki.fi/palvelut/esittely.aspx?s=otm-0b8aaeb0-d19b-43ab-9fc7-496f027776c4](https://www.avoin.helsinki.fi/palvelut/esittely.aspx?s=otm-37990742-5372-41ee-9bc9-a7f3795184b3) 96 | 97 | (2) Täytä tämä lomake https://docs.google.com/forms/d/e/1FAIpQLSfJxtLyWugefPSPHynZcZnrsNt4IGqhpr5M7dF5jgZZ6ASqsQ/viewform 98 | 99 | (3) lähetä email osoitteeseen matti.luukkainen@helsinki.fi Kerro emailissa opiskelijanumerosi sekä GitHub-tunnuksesi 100 | 101 | **HUOM: työtä ei arvostella jos KAIKKIA kolmea vaihetta ei ole tehty** 102 | 103 | Jos repositorio on privaatti lisää käyttäjät mluukkai ja santeri0200 collaboraattoriksi. 104 | 105 | Arvostelussa kestää noin 4 viikkoa. Saat arvosteluun liittyvän koodikatselmoinnin projektiisi GitHub-issuena 106 | -------------------------------------------------------------------------------- /library-backend.js: -------------------------------------------------------------------------------- 1 | const { ApolloServer } = require('@apollo/server') 2 | const { startStandaloneServer } = require('@apollo/server/standalone') 3 | 4 | let authors = [ 5 | { 6 | name: 'Robert Martin', 7 | id: "afa51ab0-344d-11e9-a414-719c6709cf3e", 8 | born: 1952, 9 | }, 10 | { 11 | name: 'Martin Fowler', 12 | id: "afa5b6f0-344d-11e9-a414-719c6709cf3e", 13 | born: 1963 14 | }, 15 | { 16 | name: 'Fyodor Dostoevsky', 17 | id: "afa5b6f1-344d-11e9-a414-719c6709cf3e", 18 | born: 1821 19 | }, 20 | { 21 | name: 'Joshua Kerievsky', // birthyear not known 22 | id: "afa5b6f2-344d-11e9-a414-719c6709cf3e", 23 | }, 24 | { 25 | name: 'Sandi Metz', // birthyear not known 26 | id: "afa5b6f3-344d-11e9-a414-719c6709cf3e", 27 | }, 28 | ] 29 | 30 | /* 31 | * Suomi: 32 | * Saattaisi olla järkevämpää assosioida kirja ja sen tekijä tallettamalla kirjan yhteyteen tekijän nimen sijaan tekijän id 33 | * Yksinkertaisuuden vuoksi tallennamme kuitenkin kirjan yhteyteen tekijän nimen 34 | * 35 | * English: 36 | * It might make more sense to associate a book with its author by storing the author's id in the context of the book instead of the author's name 37 | * However, for simplicity, we will store the author's name in connection with the book 38 | * 39 | * Spanish: 40 | * Podría tener más sentido asociar un libro con su autor almacenando la id del autor en el contexto del libro en lugar del nombre del autor 41 | * Sin embargo, por simplicidad, almacenaremos el nombre del autor en conexión con el libro 42 | */ 43 | 44 | let books = [ 45 | { 46 | title: 'Clean Code', 47 | published: 2008, 48 | author: 'Robert Martin', 49 | id: "afa5b6f4-344d-11e9-a414-719c6709cf3e", 50 | genres: ['refactoring'] 51 | }, 52 | { 53 | title: 'Agile software development', 54 | published: 2002, 55 | author: 'Robert Martin', 56 | id: "afa5b6f5-344d-11e9-a414-719c6709cf3e", 57 | genres: ['agile', 'patterns', 'design'] 58 | }, 59 | { 60 | title: 'Refactoring, edition 2', 61 | published: 2018, 62 | author: 'Martin Fowler', 63 | id: "afa5de00-344d-11e9-a414-719c6709cf3e", 64 | genres: ['refactoring'] 65 | }, 66 | { 67 | title: 'Refactoring to patterns', 68 | published: 2008, 69 | author: 'Joshua Kerievsky', 70 | id: "afa5de01-344d-11e9-a414-719c6709cf3e", 71 | genres: ['refactoring', 'patterns'] 72 | }, 73 | { 74 | title: 'Practical Object-Oriented Design, An Agile Primer Using Ruby', 75 | published: 2012, 76 | author: 'Sandi Metz', 77 | id: "afa5de02-344d-11e9-a414-719c6709cf3e", 78 | genres: ['refactoring', 'design'] 79 | }, 80 | { 81 | title: 'Crime and punishment', 82 | published: 1866, 83 | author: 'Fyodor Dostoevsky', 84 | id: "afa5de03-344d-11e9-a414-719c6709cf3e", 85 | genres: ['classic', 'crime'] 86 | }, 87 | { 88 | title: 'Demons', 89 | published: 1872, 90 | author: 'Fyodor Dostoevsky', 91 | id: "afa5de04-344d-11e9-a414-719c6709cf3e", 92 | genres: ['classic', 'revolution'] 93 | }, 94 | ] 95 | 96 | /* 97 | you can remove the placeholder query once your first one has been implemented 98 | */ 99 | 100 | const typeDefs = ` 101 | type Query { 102 | dummy: Int 103 | } 104 | ` 105 | 106 | const resolvers = { 107 | Query: { 108 | dummy: () => 0 109 | } 110 | } 111 | 112 | const server = new ApolloServer({ 113 | typeDefs, 114 | resolvers, 115 | }) 116 | 117 | startStandaloneServer(server, { 118 | listen: { port: 4000 }, 119 | }).then(({ url }) => { 120 | console.log(`Server ready at ${url}`) 121 | }) 122 | -------------------------------------------------------------------------------- /library-schema.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const mongoose = require('mongoose') 3 | 4 | const schema = new mongoose.Schema({ 5 | title: { 6 | type: String, 7 | required: true, 8 | unique: true, 9 | minlength: 2 10 | }, 11 | published: { 12 | type: Number, 13 | }, 14 | author: { 15 | type: mongoose.Schema.Types.ObjectId, 16 | ref: 'Author' 17 | }, 18 | genres: [ 19 | { type: String } 20 | ] 21 | }) 22 | 23 | module.exports = mongoose.model('Book', schema) 24 | ``` 25 | 26 | ```js 27 | const mongoose = require('mongoose') 28 | 29 | const schema = new mongoose.Schema({ 30 | name: { 31 | type: String, 32 | required: true, 33 | unique: true, 34 | minlength: 4 35 | }, 36 | born: { 37 | type: Number, 38 | }, 39 | }) 40 | 41 | module.exports = mongoose.model('Author', schema) 42 | ``` 43 | -------------------------------------------------------------------------------- /notes-bootstrap.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import { useState } from 'react' 3 | import { Table, Form, Button, Alert, Navbar, Nav } from 'react-bootstrap' 4 | 5 | import { 6 | BrowserRouter as Router, 7 | Routes, 8 | Route, 9 | Link, 10 | Navigate, 11 | useNavigate, 12 | useMatch 13 | } from "react-router-dom" 14 | 15 | const Home = () => ( 16 |
17 |

TKTL notes app

18 |

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

19 |
20 | ) 21 | 22 | const Note = ({ note }) => { 23 | return ( 24 |
25 |

{note.content}

26 |
{note.user}
27 |
{note.important ? 'important' : ''}
28 |
29 | ) 30 | } 31 | 32 | const Notes = ({ notes }) => ( 33 |
34 |

Notes

35 | 36 | 37 | {notes.map(note => 38 | 39 | 44 | 47 | 48 | )} 49 | 50 |
40 | 41 | {note.content} 42 | 43 | 45 | {note.user} 46 |
51 |
52 | ) 53 | 54 | const Users = () => ( 55 |
56 |

TKTL notes app

57 |
    58 |
  • Matti Luukkainen
  • 59 |
  • Juha Tauriainen
  • 60 |
  • Arto Hellas
  • 61 |
62 |
63 | ) 64 | 65 | const Login = (props) => { 66 | const navigate = useNavigate() 67 | 68 | const onSubmit = (event) => { 69 | event.preventDefault() 70 | props.onLogin('mluukkai') 71 | navigate('/') 72 | } 73 | 74 | return ( 75 |
76 |

login

77 |
78 | 79 | username: 80 | 84 | password: 85 | 88 | 91 | 92 |
93 |
94 | ) 95 | } 96 | 97 | const App = () => { 98 | const [notes, setNotes] = useState([ 99 | { 100 | id: 1, 101 | content: 'HTML is easy', 102 | important: true, 103 | user: 'Matti Luukkainen' 104 | }, 105 | { 106 | id: 2, 107 | content: 'Browser can execute only JavaScript', 108 | important: false, 109 | user: 'Matti Luukkainen' 110 | }, 111 | { 112 | id: 3, 113 | content: 'Most important methods of HTTP-protocol are GET and POST', 114 | important: true, 115 | user: 'Arto Hellas' 116 | } 117 | ]) 118 | 119 | const [user, setUser] = useState(null) 120 | const [message, setMessage] = useState(null) 121 | 122 | const match = useMatch('/notes/:id') 123 | 124 | const note = match 125 | ? notes.find(note => note.id === Number(match.params.id)) 126 | : null 127 | 128 | const login = (user) => { 129 | setUser(user) 130 | setMessage(`welcome ${user}`) 131 | setTimeout(() => { 132 | setMessage(null) 133 | }, 10000) 134 | } 135 | 136 | const padding = { 137 | padding: 5 138 | } 139 | 140 | return ( 141 |
142 | {(message && 143 | 144 | {message} 145 | 146 | )} 147 | 148 | 149 | 150 | 151 | 168 | 169 | 170 | 171 | 172 | } /> 173 | } /> 174 | : } /> 175 | } /> 176 | } /> 177 | 178 |
179 |
180 | Note app, Department of Computer Science 2023 181 |
182 |
183 | ) 184 | } 185 | 186 | ReactDOM.createRoot(document.getElementById('root')).render( 187 | 188 | 189 | 190 | ) 191 | -------------------------------------------------------------------------------- /notes-materialui.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import { useState } from 'react' 3 | 4 | import { 5 | Container, 6 | Table, 7 | TableBody, 8 | TableCell, 9 | TableContainer, 10 | TableRow, 11 | Paper, 12 | TextField, 13 | Button, 14 | AppBar, 15 | Toolbar, 16 | Alert 17 | } from '@mui/material' 18 | 19 | 20 | import { 21 | BrowserRouter as Router, 22 | Routes, 23 | Route, 24 | Link, 25 | Navigate, 26 | useNavigate, 27 | useMatch 28 | } from "react-router-dom" 29 | 30 | 31 | const Home = () => ( 32 |
33 |

TKTL notes app

34 |

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

35 |
36 | ) 37 | 38 | const Note = ({ note }) => { 39 | 40 | return ( 41 |
42 |

{note.content}

43 |
{note.user}
44 |
{note.important ? 'important' : ''}
45 |
46 | ) 47 | } 48 | 49 | const Notes = ({ notes }) => ( 50 |
51 |

Notes

52 | 53 | 54 | 55 | 56 | {notes.map(note => ( 57 | 58 | 59 | {note.content} 60 | 61 | 62 | {note.name} 63 | 64 | 65 | ))} 66 | 67 |
68 |
69 |
70 | ) 71 | 72 | const Users = () => ( 73 |
74 |

TKTL notes app

75 |
    76 |
  • Matti Luukkainen
  • 77 |
  • Juha Tauriainen
  • 78 |
  • Arto Hellas
  • 79 |
80 |
81 | ) 82 | 83 | const Login = (props) => { 84 | const navigate = useNavigate() 85 | 86 | const onSubmit = (event) => { 87 | event.preventDefault() 88 | props.onLogin('mluukkai') 89 | navigate('/') 90 | } 91 | 92 | return ( 93 |
94 |

login

95 |
96 |
97 | 98 |
99 |
100 | 101 |
102 |
103 | 106 |
107 |
108 |
109 | ) 110 | } 111 | 112 | const App = () => { 113 | const [notes, setNotes] = useState([ 114 | { 115 | id: 1, 116 | content: 'HTML is easy', 117 | important: true, 118 | user: 'Matti Luukkainen' 119 | }, 120 | { 121 | id: 2, 122 | content: 'Browser can execute only JavaScript', 123 | important: false, 124 | user: 'Matti Luukkainen' 125 | }, 126 | { 127 | id: 3, 128 | content: 'Most important methods of HTTP-protocol are GET and POST', 129 | important: true, 130 | user: 'Arto Hellas' 131 | } 132 | ]) 133 | 134 | const [user, setUser] = useState(null) 135 | const [message, setMessage] = useState(null) 136 | 137 | const match = useMatch('/notes/:id') 138 | 139 | const note = match 140 | ? notes.find(note => note.id === Number(match.params.id)) 141 | : null 142 | 143 | const login = (user) => { 144 | setUser(user) 145 | setMessage(`welcome ${user}`) 146 | setTimeout(() => { 147 | setMessage(null) 148 | }, 10000) 149 | } 150 | 151 | const padding = { 152 | padding: 5 153 | } 154 | 155 | return ( 156 | 157 |
158 | {(message && 159 | 160 | {message} 161 | 162 | )} 163 |
164 | 165 | 166 | 169 | 172 | 175 | {user 176 | ? {user} logged in 177 | : 180 | } 181 | 182 | 183 | 184 | 185 | } /> 186 | } /> 187 | : } /> 188 | } /> 189 | } /> 190 | 191 |
192 |
193 | Note app, Department of Computer Science 2022 194 |
195 |
196 | ) 197 | } 198 | 199 | ReactDOM.createRoot(document.getElementById('root')).render() 200 | -------------------------------------------------------------------------------- /patients-full.ts: -------------------------------------------------------------------------------- 1 | import { Patient, Gender } from '../src/types'; 2 | 3 | const patients: Patient[] = [ 4 | { 5 | id: 'd2773336-f723-11e9-8f0b-362b9e155667', 6 | name: 'John McClane', 7 | dateOfBirth: '1986-07-09', 8 | ssn: '090786-122X', 9 | gender: Gender.Male, 10 | occupation: 'New york city cop', 11 | entries: [ 12 | { 13 | id: 'd811e46d-70b3-4d90-b090-4535c7cf8fb1', 14 | date: '2015-01-02', 15 | type: 'Hospital', 16 | specialist: 'MD House', 17 | diagnosisCodes: ['S62.5'], 18 | description: 19 | "Healing time appr. 2 weeks. patient doesn't remember how he got the injury.", 20 | discharge: { 21 | date: '2015-01-16', 22 | criteria: 'Thumb has healed.', 23 | }, 24 | }, 25 | ], 26 | }, 27 | { 28 | id: 'd2773598-f723-11e9-8f0b-362b9e155667', 29 | name: 'Martin Riggs', 30 | dateOfBirth: '1979-01-30', 31 | ssn: '300179-777A', 32 | gender: Gender.Male, 33 | occupation: 'Cop', 34 | entries: [ 35 | { 36 | id: 'fcd59fa6-c4b4-4fec-ac4d-df4fe1f85f62', 37 | date: '2019-08-05', 38 | type: 'OccupationalHealthcare', 39 | specialist: 'MD House', 40 | employerName: 'HyPD', 41 | diagnosisCodes: ['Z57.1', 'Z74.3', 'M51.2'], 42 | description: 43 | 'Patient mistakenly found himself in a nuclear plant waste site without protection gear. Very minor radiation poisoning. ', 44 | sickLeave: { 45 | startDate: '2019-08-05', 46 | endDate: '2019-08-28', 47 | }, 48 | }, 49 | ], 50 | }, 51 | { 52 | id: 'd27736ec-f723-11e9-8f0b-362b9e155667', 53 | name: 'Hans Gruber', 54 | dateOfBirth: '1970-04-25', 55 | ssn: '250470-555L', 56 | gender: Gender.Other, 57 | occupation: 'Technician', 58 | entries: [], 59 | }, 60 | { 61 | id: 'd2773822-f723-11e9-8f0b-362b9e155667', 62 | name: 'Dana Scully', 63 | dateOfBirth: '1974-01-05', 64 | ssn: '050174-432N', 65 | gender: Gender.Female, 66 | occupation: 'Forensic Pathologist', 67 | entries: [ 68 | { 69 | id: 'b4f4eca1-2aa7-4b13-9a18-4a5535c3c8da', 70 | date: '2019-10-20', 71 | specialist: 'MD House', 72 | type: 'HealthCheck', 73 | description: 'Yearly control visit. Cholesterol levels back to normal.', 74 | healthCheckRating: 0, 75 | }, 76 | { 77 | id: 'fcd59fa6-c4b4-4fec-ac4d-df4fe1f85f62', 78 | date: '2019-09-10', 79 | specialist: 'MD House', 80 | type: 'OccupationalHealthcare', 81 | employerName: 'FBI', 82 | description: 'Prescriptions renewed.', 83 | }, 84 | { 85 | id: '37be178f-a432-4ba4-aac2-f86810e36a15', 86 | date: '2018-10-05', 87 | specialist: 'MD House', 88 | type: 'HealthCheck', 89 | description: 90 | 'Yearly control visit. Due to high cholesterol levels recommended to eat more vegetables.', 91 | healthCheckRating: 1, 92 | }, 93 | ], 94 | }, 95 | { 96 | id: 'd2773c6e-f723-11e9-8f0b-362b9e155667', 97 | name: 'Matti Luukkainen', 98 | dateOfBirth: '1971-04-09', 99 | ssn: '090471-8890', 100 | gender: Gender.Male, 101 | occupation: 'Digital evangelist', 102 | entries: [ 103 | { 104 | id: '54a8746e-34c4-4cf4-bf72-bfecd039be9a', 105 | date: '2019-05-01', 106 | specialist: 'Dr Byte House', 107 | type: 'HealthCheck', 108 | description: 'Digital overdose, very bytestatic. Otherwise healthy.', 109 | healthCheckRating: 0, 110 | }, 111 | ], 112 | }, 113 | ]; 114 | 115 | export default patients; 116 | -------------------------------------------------------------------------------- /patients.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "d2773336-f723-11e9-8f0b-362b9e155667", 4 | "name": "John McClane", 5 | "dateOfBirth": "1986-07-09", 6 | "ssn": "090786-122X", 7 | "gender": "male", 8 | "occupation": "New york city cop" 9 | }, 10 | { 11 | "id": "d2773598-f723-11e9-8f0b-362b9e155667", 12 | "name": "Martin Riggs", 13 | "dateOfBirth": "1979-01-30", 14 | "ssn": "300179-77A", 15 | "gender": "male", 16 | "occupation": "Cop" 17 | }, 18 | { 19 | "id": "d27736ec-f723-11e9-8f0b-362b9e155667", 20 | "name": "Hans Gruber", 21 | "dateOfBirth": "1970-04-25", 22 | "ssn": "250470-555L", 23 | "gender": "male", 24 | "occupation": "Technician" 25 | }, 26 | { 27 | "id": "d2773822-f723-11e9-8f0b-362b9e155667", 28 | "name": "Dana Scully", 29 | "dateOfBirth": "1974-01-05", 30 | "ssn": "050174-432N", 31 | "gender": "female", 32 | "occupation": "Forensic Pathologist" 33 | }, 34 | { 35 | "id": "d2773c6e-f723-11e9-8f0b-362b9e155667", 36 | "name": "Matti Luukkainen", 37 | "dateOfBirth": "1971-04-09", 38 | "ssn": "090471-8890", 39 | "gender": "male", 40 | "occupation": "Digital evangelist" 41 | } 42 | ] 43 | -------------------------------------------------------------------------------- /patients.ts: -------------------------------------------------------------------------------- 1 | const data = [ 2 | { 3 | "id": "d2773336-f723-11e9-8f0b-362b9e155667", 4 | "name": "John McClane", 5 | "dateOfBirth": "1986-07-09", 6 | "ssn": "090786-122X", 7 | "gender": "male", 8 | "occupation": "New york city cop" 9 | }, 10 | { 11 | "id": "d2773598-f723-11e9-8f0b-362b9e155667", 12 | "name": "Martin Riggs", 13 | "dateOfBirth": "1979-01-30", 14 | "ssn": "300179-77A", 15 | "gender": "male", 16 | "occupation": "Cop" 17 | }, 18 | { 19 | "id": "d27736ec-f723-11e9-8f0b-362b9e155667", 20 | "name": "Hans Gruber", 21 | "dateOfBirth": "1970-04-25", 22 | "ssn": "250470-555L", 23 | "gender": "other", 24 | "occupation": "Technician" 25 | }, 26 | { 27 | "id": "d2773822-f723-11e9-8f0b-362b9e155667", 28 | "name": "Dana Scully", 29 | "dateOfBirth": "1974-01-05", 30 | "ssn": "050174-432N", 31 | "gender": "female", 32 | "occupation": "Forensic Pathologist" 33 | }, 34 | { 35 | "id": "d2773c6e-f723-11e9-8f0b-362b9e155667", 36 | "name": "Matti Luukkainen", 37 | "dateOfBirth": "1971-04-09", 38 | "ssn": "090471-8890", 39 | "gender": "male", 40 | "occupation": "Digital evangelist" 41 | } 42 | ]; 43 | 44 | export default data; 45 | -------------------------------------------------------------------------------- /project.md: -------------------------------------------------------------------------------- 1 | # Full Stack -web development project 5, 7 or 10 credits 2 | 3 | In the project you create an application of your choice with React and/or Node. Also a mobile application created with React Native is possible. 4 | 5 | The backend for the application does not have to be in Node. Backend does not have to be self made, ready interfaces or services such as Firebase will do. Frontend is also possible to make with some other reasonable way than React, e.g. with Vue.js, in which case it is recommended for the application have a Node backend. 6 | 7 | Amount of credits is determined by the working hours, where one credit is 17.5 hours. The limits are 87.5 hours for 5 credits, 122.5 hours for 7 credits and 175 hours for 10 credits. It is also calculated that 9.5 hours of independent study of the techniques used in the practice work will take place for each credit. 8 | 9 | The grading is pass/fail. 10 | 11 | You will automatically get a failing grade if the application is not done by yourself or if the hour keeping and GitHub commit history do not match reasonably. All the hours put into the project are counted, so even working a backend with something other than Node, counts. 12 | 13 | Reviewing course material, however, _does not_ count. 14 | 15 | **For a passing grade** it is expected that the used hours show in the end product in some meaningful way. If eg. applying for 10 credits, we expect that your application has more than one or two functionalities. An example of a good base line is that your application performs various database operations, ie. CRUD operations (create, read, update, and save). Alternatively it employs only some (but more than one) of them in various ways. Testing and for example automating the deployment pipeline and other such notable configurations are naturally positively counted towards your project's scale. 16 | 17 | 18 | If you project has very limited functionalities/techniques, you might be asked to expand your project a bit, especially for the 7 and 10 credit versions. 19 | 20 | **Please note:** It is not sufficient that implemented features work only on your machine, but they also must work in your application that is deployed to the internet. 21 | 22 | It is also possible to do the project as a pair or a team. 23 | 24 | The Discord channel for the Full Stack Course https://study.cs.helsinki.fi/discord/join/fullstack works also for this project. 25 | 26 | ## Attending and registering your subject 27 | 28 | There is no need to register for the course if you are using the technologies that the Full stack course covers. If you have doubts, send email to matti.luukkainen@helsinki.fi 29 | 30 | ## Guidelines for Using Video Tutorials 31 | 32 | Using video tutorials can be a helpful way to learn new skills and understand concepts, especially when you're starting with a new technology or framework. However, for the full stack course project, it's essential to demonstrate your own understanding and abilities learned in the course. To ensure your project showcases your personal skills and effort, please follow these guidelines when using video tutorials as a reference: 33 | 34 | ### 1. Use Tutorials for Learning, Not Direct Copying 35 | - **Purpose:** Tutorials should be used as learning resources to help you understand concepts, not as templates to directly replicate. 36 | - **Expectation:** After watching a tutorial, apply the knowledge gained to build something unique or extend the tutorial's example with your own features. 37 | 38 | ### 2. Make Substantial Modifications 39 | - **Originality:** Your project should be distinct from the tutorial in meaningful ways. This could include changes to the UI, added features, different functionality, or using a different tech stack. 40 | - **Examples of Modifications:** 41 | - Adding new features not covered in the tutorial (e.g., user profiles, notifications, search functionality). 42 | - Implementing different or more advanced techniques (e.g., integrating an API, using a different database). 43 | - Styling the application differently or using an alternative frontend framework. 44 | 45 | ### 3. Add Unique Features 46 | - **Customization:** Introduce features that were not part of the tutorial to showcase your skills. These could be features relevant to the course objectives or ones that enhance the user experience. 47 | - **Examples of Unique Features:** 48 | - For a social media app: Adding groups, chat functionality, or image upload features. 49 | - For an e-commerce app: Adding a wishlist, reviews, or a payment gateway. 50 | 51 | ### 4. Explain the Differences Clearly 52 | - **Documentation:** When submitting your project, include a brief document explaining which parts of your application were inspired by the tutorial and what changes or enhancements you made. 53 | - **Justification:** Specify why you chose to make these changes and how they improve or differentiate your project. 54 | 55 | ### 5. Consider Going Beyond the Basics 56 | - **Advanced Implementations:** To further demonstrate your understanding, consider adding advanced features not covered in the tutorial, such as: 57 | - **CI/CD Pipelines:** Setting up continuous integration and deployment for your application. 58 | - **Testing:** Implementing unit tests, integration tests, or end-to-end tests. 59 | - **Security Enhancements:** Adding user authentication, data validation, and secure handling of sensitive information. 60 | 61 | ### 6. Avoid Plagiarism 62 | - **Original Work:** Copying code directly from a tutorial without modification is considered plagiarism. Your project must reflect your own efforts and understanding. 63 | - **Credit the Source:** If you use any specific code snippets or ideas from a tutorial, make sure to give appropriate credit in your code comments or documentation. 64 | 65 | By following these guidelines, you can use video tutorials effectively as a learning tool while ensuring that your project reflects your own creativity and skills. 66 | 67 | 68 | ## Requirements for the returned application 69 | 70 | - In the README of your repository you must have all of the following: 71 | - Link to an address, where the application is running. If you're creating a mobile application, demo will be scheduled individually. 72 | - Some user instructions and explanation of the purpose of your project (ie. what does it do), or a link to them 73 | - Link to work hours listing. Do it as a .md, pdf, or in some way that is immediately visible (ie. no file download required) 74 | - Sufficient precision level for time keeping is [this](https://github.com/mluukkai/OtmTodoApp/blob/master/dokumentaatio/tuntikirjanpito.md) 75 | - The time keeping **must include the total sum of spent hours** 76 | - Time keeping and repository commit history must reasonably match each other. Do intermediate commits! 77 | - If working with a pair or a group, everyone keeps their own hours. 78 | - If your project is split into several repositories, a link to the other repositories 79 | 80 | - Your repository and code base must be presentable. No out-commented or otherwise unused code or such. 81 | 82 | If some of the above mentioned requirements are not satisfied when applying for credits, your project will not be graded and you will be asked to fix them. 83 | 84 | Why? 85 | 86 | Your repository is your greeting card especially when applying for a job. No recruiter will appreciate having to dig through a messy repository to evaluate your skills. (Neither will instructors) Many will not do it at all. It's a good idea to stick to good practices whenever possible. 87 | 88 | ## Finishing the application and grading 89 | 90 | When your project is ready for grading **do ALL of the following**: 91 | 92 | (1) Register in open university to a course instance that corresponds your working hours 93 | - FS project (5 credits): [https://www.avoin.helsinki.fi/palvelut/esittely.aspx?s=otm-213e3838-c757-4839-bdb9-69dc7e23571c](https://www.avoin.helsinki.fi/palvelut/esittely.aspx?s=otm-7b7d5cc3-2bb5-435d-8c33-752a51238f00) 94 | - FS project (7 credits): https://www.avoin.helsinki.fi/palvelut/esittely.aspx?s=otm-5b229b06-d94e-483d-becf-e50c0f30ac47 95 | - FS project (10 credits): [https://www.avoin.helsinki.fi/palvelut/esittely.aspx?s=otm-0b8aaeb0-d19b-43ab-9fc7-496f027776c4](https://www.avoin.helsinki.fi/palvelut/esittely.aspx?s=otm-37990742-5372-41ee-9bc9-a7f3795184b3) 96 | 97 | (2) Fill this form https://docs.google.com/forms/d/e/1FAIpQLSfJxtLyWugefPSPHynZcZnrsNt4IGqhpr5M7dF5jgZZ6ASqsQ/viewform 98 | 99 | (3) **and send an email** to matti.luukkainen@helsinki.fi 100 | 101 | In the email you need to specify your student number and GitHub account 102 | 103 | **The project will not be graded untill ALL THREE steps have been completed** 104 | 105 | If you have a private repository, add users mluukkai and santeri0200 as a collaborator. 106 | 107 | The review takes roughly 4 weeks. You will get some feedback on your project as a GitHub issue. 108 | -------------------------------------------------------------------------------- /router-app-v1.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import { useState } from 'react' 3 | 4 | import { 5 | BrowserRouter as Router, 6 | Routes, 7 | Route, 8 | Link, 9 | Navigate, 10 | useParams, 11 | useNavigate, 12 | } from "react-router-dom" 13 | 14 | 15 | const Home = () => ( 16 |
17 |

TKTL notes app

18 |

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

19 |
20 | ) 21 | 22 | const Note = ({ notes }) => { 23 | const id = useParams().id 24 | const note = notes.find(n => n.id === Number(id)) 25 | return ( 26 |
27 |

{note.content}

28 |
{note.user}
29 |
{note.important ? 'important' : ''}
30 |
31 | ) 32 | } 33 | 34 | const Notes = ({ notes }) => ( 35 |
36 |

Notes

37 |
    38 | {notes.map(note => 39 |
  • 40 | {note.content} 41 |
  • 42 | )} 43 |
44 |
45 | ) 46 | 47 | const Users = () => ( 48 |
49 |

TKTL notes app

50 |
    51 |
  • Matti Luukkainen
  • 52 |
  • Juha Tauriainen
  • 53 |
  • Arto Hellas
  • 54 |
55 |
56 | ) 57 | 58 | const Login = (props) => { 59 | const navigate = useNavigate() 60 | 61 | const onSubmit = (event) => { 62 | event.preventDefault() 63 | props.onLogin('mluukkai') 64 | navigate('/') 65 | } 66 | 67 | return ( 68 |
69 |

login

70 |
71 |
72 | username: 73 |
74 |
75 | password: 76 |
77 | 78 |
79 |
80 | ) 81 | } 82 | 83 | const App = () => { 84 | const [notes, setNotes] = useState([ 85 | { 86 | id: 1, 87 | content: 'HTML is easy', 88 | important: true, 89 | user: 'Matti Luukkainen' 90 | }, 91 | { 92 | id: 2, 93 | content: 'Browser can execute only JavaScript', 94 | important: false, 95 | user: 'Matti Luukkainen' 96 | }, 97 | { 98 | id: 3, 99 | content: 'Most important methods of HTTP-protocol are GET and POST', 100 | important: true, 101 | user: 'Arto Hellas' 102 | } 103 | ]) 104 | 105 | const [user, setUser] = useState(null) 106 | 107 | const login = (user) => { 108 | setUser(user) 109 | } 110 | 111 | const padding = { 112 | padding: 5 113 | } 114 | 115 | return ( 116 |
117 | 118 |
119 | home 120 | notes 121 | users 122 | {user 123 | ? {user} logged in 124 | : login 125 | } 126 |
127 | 128 | 129 | } /> 130 | } /> 131 | : } /> 132 | } /> 133 | } /> 134 | 135 |
136 |
137 |
138 | Note app, Department of Computer Science 2023 139 |
140 |
141 | ) 142 | } 143 | 144 | ReactDOM.createRoot(document.getElementById('root')).render() 145 | -------------------------------------------------------------------------------- /router-app-v2.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import { useState } from 'react' 3 | 4 | import { 5 | BrowserRouter as Router, 6 | Routes, 7 | Route, 8 | Link, 9 | Navigate, 10 | useParams, 11 | useNavigate, 12 | useMatch 13 | } from "react-router-dom" 14 | 15 | 16 | const Home = () => ( 17 |
18 |

TKTL notes app

19 |

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

20 |
21 | ) 22 | 23 | const Note = ({ note }) => { 24 | 25 | return ( 26 |
27 |

{note.content}

28 |
{note.user}
29 |
{note.important ? 'important' : ''}
30 |
31 | ) 32 | } 33 | 34 | const Notes = ({ notes }) => ( 35 |
36 |

Notes

37 |
    38 | {notes.map(note => 39 |
  • 40 | {note.content} 41 |
  • 42 | )} 43 |
44 |
45 | ) 46 | 47 | const Users = () => ( 48 |
49 |

TKTL notes app

50 |
    51 |
  • Matti Luukkainen
  • 52 |
  • Juha Tauriainen
  • 53 |
  • Arto Hellas
  • 54 |
55 |
56 | ) 57 | 58 | const Login = (props) => { 59 | const navigate = useNavigate() 60 | 61 | const onSubmit = (event) => { 62 | event.preventDefault() 63 | props.onLogin('mluukkai') 64 | navigate('/') 65 | } 66 | 67 | return ( 68 |
69 |

login

70 |
71 |
72 | username: 73 |
74 |
75 | password: 76 |
77 | 78 |
79 |
80 | ) 81 | } 82 | 83 | const App = () => { 84 | const [notes, setNotes] = useState([ 85 | { 86 | id: 1, 87 | content: 'HTML is easy', 88 | important: true, 89 | user: 'Matti Luukkainen' 90 | }, 91 | { 92 | id: 2, 93 | content: 'Browser can execute only JavaScript', 94 | important: false, 95 | user: 'Matti Luukkainen' 96 | }, 97 | { 98 | id: 3, 99 | content: 'Most important methods of HTTP-protocol are GET and POST', 100 | important: true, 101 | user: 'Arto Hellas' 102 | } 103 | ]) 104 | 105 | const [user, setUser] = useState(null) 106 | 107 | const match = useMatch('/notes/:id') 108 | 109 | const note = match 110 | ? notes.find(note => note.id === Number(match.params.id)) 111 | : null 112 | 113 | 114 | const login = (user) => { 115 | setUser(user) 116 | } 117 | 118 | const padding = { 119 | padding: 5 120 | } 121 | 122 | return ( 123 |
124 |
125 | home 126 | notes 127 | users 128 | {user 129 | ? {user} logged in 130 | : login 131 | } 132 |
133 | 134 | } /> 135 | } /> 136 | : } /> 137 | } /> 138 | } /> 139 | 140 |
141 |
142 | Note app, Department of Computer Science 2022 143 |
144 |
145 | ) 146 | } 147 | 148 | ReactDOM.createRoot(document.getElementById('root')).render() 149 | --------------------------------------------------------------------------------