Wiki emile - EmileKost/Discover Wiki

Product Biografie Discover, Emile Kost

In deze wiki ga ik mijn gehele proces doorlopen en stap voor stap vertellen wat ik elke week heb gedaan. Ik heb heel erg veel geleerd bij de meesterproef en ik ben elke week dichterbij het eindresultaat gekomen.

Week 0 en 1

In deze andere halve week lag de focus vooral op het contact leggen met de opdrachtgever. Onze opdrachtgever is Thierno Balde. Wij hadden dinsdag ons eerste gesprek met Thierno. Voor het gesprek met Thierno heb ik samen met Joep een vragenlijst opgesteld met punten die wij graag zouden willen weten. Na het beantwoorden van onze vragen hadden wij een beter beeld over wat er van ons verwacht wordt. Na ons eerste gesprek zijn we begonnen met het opstellen van een debriefing die wij aan onze begeleider Koop hebben voorgelegd. Naast het kennismaken met de opdrachtgever heb ik allereerst onderzoek gedaan naar de DISC test en hoe je deze zou moeten opzetten in eventuele code later. De eerste week was vanwege Hemelvaart een erg korte week, dus is deze week puur en alleen geweest voor het contact leggen en oriënteren met de opdrachtgever.

De vragen voor Thierno

  • Wat hebben jullie nu?
  • Hoe komen jullie aan de vacatures?
  • Is er een bepaalde huisstijl waar wij ons aan moeten houden?
  • In hoeverre moet de backend worden uitgewerkt?
  • Hoe gaan we de wekelijkse oplever momenten doen?

Opdracht beschrijving gebaseerd op gesprek met Thierno eerste week

De aanleiding van deze opdracht is dat veel mensen na het afronden van hun opleiding niet een duidelijk beeld hebben van een baan die zij zoeken en wat zij zelf willen. De doelstelling van dit project is dat de gebruikers een beter beeld van hun persoonlijkheid krijgen. Aan de hand van de persoonlijkheidstest krijgt de gebruiker een overzichtelijke lijst van passende vacatures (disc model). Eventueel waar nodig worden vervolgstappen aangeboden zoals omscholingsopties. Ook is nog een doel dat bedrijven kunnen zoeken naar relevante kandidaten. Wij werken samen met de CMD Agency, zij hebben al vooronderzoek gedaan en aan de hand daar van gaan zij uiteindelijk een ontwerp aan ons presenteren. Wij gaan dan kijken in hoeverre dat haalbaar is met betrekking tot de technische functionaliteiten. Aan ons is nu de taak om te experimenteren met de technische functionaliteiten. Wij gaan prototypes maken en op basis van iteraties dit uiteindelijk toepassen op de website.   De eindgebruikers zijn werkzoekenden die niet goed weten wat voor baan zij zoeken, en werkgevers die op zoek zijn naar nieuwe werknemers.

Wat ging goed?

  • Het contact met de opdrachtgever ging vrij soepel
  • Duidelijke debriefing

Wat ging minder goed?

  • Ik wist niet dat we zouden samenwerken met CMD Agency

Week 2

In de eerste week werd de focus heel erg gelegd op het scrapen van vacatures van andere vacaturebanken. Dit blijkt echter best wel illegaal te zijn en het een lastige code omdat de meeste vacaturebanken qua code heel erg slecht zijn gestructureerd waardoor het lastig is om met een blok code bij verschillende vacaturebanken alle vacatures op te halen. Dit was gelukkig ook niet voor mij een focus punt. Mijn doel was om vacatures te gaan plaatsen in een database en ze vanaf daar op te halen. Toch wilde ik wel graag het scrapen uit proberen.

Scrapen van data

 const path = require('path');
 const fs = require('fs');
 const bodyParser = require('body-parser');

import cheerio from 'cheerio';
import fetch from 'node-fetch';
const getRawJobData = (url) => {
    return fetch(url)
    .then((response) => response.text())
    .then((data) => {
        return data
    })
}

// URL which is necessary to fetch
const url = 'https://www.werkzoeken.nl/vacatures/?what=&r=20&fid=&ga_network=g&ga_keyword=jobbird&ga_matchtype=e&ga_targetid=kwd-37698426697&ga_placement=&ga_loc_physical_ms=1010542&ga_loc_interest_ms=&utm_source=google&source=go42c77153ada88cdcdf0902c06169b3&utm_medium=cpc&utm_campaign=[a]_Concurrenten_-_locatie_targeting&campaign_source=vo&gclid=Cj0KCQjwhLKUBhDiARIsAMaTLnGw8zeJ_5Uik5fx9HdIn_Dc7xrC00JEpp47rTTxJlRu0CR7r2YHU3waAsmJEALw_wcB';

const getJobOffers = async () => {
    const jobOffersRawData = await getRawJobData(url);
    
    // console.log(jobOffersRawData);
}

getJobOffers();

const parsedData = cheerio.load(
    `
    <p id="desc_cv" class="display_none">
    Een account is nodig om je veilig toegang te geven tot de CV documenten van onze leden. Je kunt hierna gelijk door 105.126 CV's zoeken en het CV downloaden. </p>
    `
app.post('/', (req, res) => {
    let username = JSON.stringify(req.body)
    fs.writeFile('usernames.json', username, 'utf8', cb => {
        console.log(username);
    })
    res.render('index')
})

Om html te kunnen scrapen van een website heb je verschillende packages nodig. De eerste is Cheerio, die het scrapen daadwerkelijk mogelijk maakt. Daarnaast had ik ook nog express body parser en node-fetch nodig om de waarde van de html op te halen en om de functie fetch() te gebruiken om zo de url van de website te kunnen ophalen. Hierdoor kon ik daadwerkelijk de data ophalen van de website. De functies werken als volgt: Eerst moet je een variabele maken van de url van de website waarvan je de tekst content wilt hebben. Daarna kan je door middel van node-fetch de functie fetch gebruiken om zo de url op te halen. Als dit gedaan is maak je van de response een tekst door middel van .text. Daarna kan je door middel van cheerio.load precies aangeven welk element je wilt renderen op jouw eigen pagina. Het is hierbij wel belangrijk dat elementen een class hebben.

Al snel werd mij duidelijk dat dit niet de juiste manier zal zijn voor ons eindproduct. Scrapen is namelijk vaak illegaal. Daarnaast zijn de meeste vacaturebanken zeer slecht gestructureerd met onduidelijke classes en variërende structuren. Als de opdrachtgever daadwerkelijk een vacaturebank wil uitwerken zullen zij zelf vacatures moeten leveren. Het is mijn bedoeling om deze vacatures zelf te linken aan een database om ze hiervandaan te gaan ophalen. Dit is de juiste manier om overzichtelijk en legaal vacature data te plaatsen op de website.

Registreer en login functie

In week twee ben ik ook nog aan de slag gegaan met het login systeem van onze website. Het is namelijk belangrijk dat data zoals de uitslag van de DISC persoonlijkheidstest gekoppeld kan worden aan een bepaald persoon. Na het kijken van meerdere tutorials en mij inlezen over het onderwerp ben ik uiteindelijk tot een oplossing gekomen. In deze week wordt de persoonsdata nog niet opgeslagen in een database maar wordt het in een lege array gezet.

App.js bestand

const bcrypt = require('bcrypt');
const passport = require('passport');

const initializePassport = require('./passport-config.js');
initializePassport(
  passport,
  email => users.find(user => user.email === email),
  id => users.find(user => user.id === id)
);

app.post('/register', checkNotAuthenticated, async (req, res) => {
  try {
    const hashedPassword = await bcrypt.hash(req.body.password, 10)
    const users = []

    users.push({
      id: Date.now().toString(),
      name: req.body.name,
      email: req.body.email,
      password: hashedPassword
    })

app.post('/login', checkNotAuthenticated, passport.authenticate('local', {
  successRedirect: '/',
  failureRedirect: '/login',
  failureFlash: true
}))

Voor het inlog systeem had ik verschillende packages nodig. De eerste is Passport. Passport is een middelware die het met ejs heel gemakkelijk maakt om een registreer en login systeem te bouwen. Door Passport kan er een account worden aangemaakt en kan het de input vergelijken om zo de juiste gebruiker met het juiste wachtwoord te valideren. De initializePassport functie roept het passport-config bestand aan die de waardes met elkaar vergelijkt. InitializePassport plaats de waarden bij de juiste variabele. De andere package is bcrypt. Dit is zodat een wachtwoord encrypted wordt gemaakt.

Passport-config.js bestand


const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');

function initialize(passport, getUserByEmail, getUserById) {
    const authenticateUser = async (email, password, done) => {
        const user = getUserByEmail(email)
        if(user == null) {
            return done(null, false, { message: 'Er is geen account met dit emailadres'});
        }

        try {
            if(await bcrypt.compare(password, user.password)) {
                return done(null, user)
            } else {
                return done(null, false, { message: 'Het wachtwoord is incorrect' })
            }
        } catch(error) {
            return done(error)
        }
    }

    passport.use(new LocalStrategy({usernameField: 'email'}, authenticateUser))
    passport.serializeUser((user, done) => done(null, user.id))
    passport.deserializeUser((id, done) => {
        return done(null, getUserById(id))
    })
}

module.exports = initialize

In het passport-config bestand wordt de input van het inlog formulier vergeleken met de verschillende gebruikersnamen en wachtwoorden. Passport zorgt ervoor deze data met elkaar wordt vergeleken en geeft waar nodig de juiste fout melding. Als Passport de juiste gebruikersnaam en het juiste wachtwoord constateert, dan wordt de gebruiker doorverwezen naar de indexpagina.

app.post('/login', checkNotAuthenticated, passport.authenticate('local', {
  successRedirect: '/',
  failureRedirect: '/login',
  failureFlash: true
}))

Door middel van succesRedirect wordt de gebruiker bij het invullen van de juiste informatie doorgeleid naar de index pagina.

Ik heb in week heel erg veel geleerd. Het viel mij op dat scrapen eigenlijk helemaal geen handige manier is om vacatures op te slaan. Dit ga ik volgende week overleggen met Thieno om zelf vacatures in een database te zetten. Het meest interessante dat ik deze week heb geleerd is het ontwikkelen van een registreer en inlog systeem. Het ontwikkelen hiervan heeft eigenlijk voor weinig problemen gezorgd omdat er al heel veel te vinden is over het ontwikkelen hiervan. Tevens is het vrijwel voor iedereen dezelfde code, wat het voor mij makkelijk maakte om het op te slaan. Het enige wat verschilt is wat je na het registreren met de data wilt doen. In mijn geval wordt dit het plaatsen van de data in een database, zodat er daadwerkelijk bestaande accounts zijn en zullen blijven bestaan.

Wat ging goed?

  • Het ontwikkelen van de registreer en login functie ging vrijwel foutloos
  • Ik kwam snel op een artikel die scrapen in Javascript helder uitlegde

Wat ging minder goed?

  • Scrapen blijkt vanwege de structuur van andere vacature websites een stuk moeilijker dan gedacht
  • Scrapen is (bijna) altijd illegaal en we kunnen daarom beter een andere methode gebruiken

Week 3

In week drie heb ik mij de eerste dag verdiept in de database service Mongodb en Mongoose. Ik heb hier verschillende artikelen en Youtube video's over bekeken om een algemeen beeld te krijgen over hoe dit zou werken. Voordat ik deze week hiermee aan de slag zou gaan wilde ik dit eerst voorleggen aan Thierno. Thierno begreep dat scrapen lastig zou worden en vond het een goed idee om te beginnen met het ontwikkelen van een database om hier de data in op te slaan. Na de goedkeuring van Thierno was mijn eerste doel om de gebruikers informatie op te slaan in Mongodb.

Mongodb en Mongoose

Account maken voor Mongodb

Voordat ik kon beginnen met Mongodb moest ik eerst een account aanmaken, in de hoop dat Mongodb een goede gratis service zou leveren. Gelukkig levert Mongodb een (in mijn ogen) redelijk uitgebreide gratis versie van hun database service's.

Opzetten en een connectie starten van de eerste database

Als je eenmaal een account heb moet je de keuze maken of je het lokaal of via de cloud wilt runnen. Ik heb zelf besloten om door middel van Mongoose het via de cloud te runnen. Dit omdat uit eerder gelezen artikelen bleek dat dit vaak gemakkelijker is, en dat je gemakkelijk via Mongoose kan communiceren tussen de website en de database. Bij het opzetten van de database hoef je de database alleen een naam te geven en wordt deze database aangemaakt. Om een connectie te krijgen moest ik eerst mongoose installeren door middel van npm en de url van deze database opvragen en hierbij mijn username en wachtwoord toevoegen om zo de connectie te maken.

Schermafbeelding 2022-06-23 om 12 03 18 Het is heel gemakkelijk om een database aan te maken.
mongoose.connect(dbURI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then((result) => app.listen(7000), console.log('Mongodb connected'))
  .catch((error) => console.log(error + 'has occured'))

const port = process.env.PORT || 7000;

Deze code is nodig om een connectie naar de database te maken. dbURI is hierin de database url die ik hier weglaat omdat de gebruikersnaam en wachtwoord hier zichtbaar zijn.

Data Schema maken voor het gebruikersaccount

Nu er eenmaal een database is, kon ik in deze database de eerste collectie voor de gebruiker aanmaken. De collectie maak je altijd op de website van Mongodb aan. Deze titel is in het meervoud. Om de structuur van data aan te geven moest ik een Javascript bestand aanmaken waarin dit wordt gestructureerd. Dit noem je een Schema.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

//This is how the structure of the data is going to look like
const blogSchema = new Schema({
    name: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true
    },
    password: {
        type: String,
        required: true
    }
}, { timestamps: true });

//Blog is user

const Blog = mongoose.model('Blog', blogSchema);
module.exports = Blog;

In het Schema geef je aan welke title's de verschillende dataobjecten hebben. Tevens kan je deze een type meegeven zoals een String of een Number. Zo voorkom je onnodige foutmeldingen en bugs. Je kan ook nog de waarde required meegeven. Hiermee kan je aangeven of een veld verplicht is voor het dataobject. Uiteindelijk moet je het Schema exporteren zodat die gebruikt kan worden. Omdat de naam het enkelvoud van de collecties naam is, weet door middel van Mongoose de database precies welke collectie bedoelt wordt.

Het opslaan van de gebruikers data in de database

Na het aanmaken van de database en de collectie erin, kon ik daadwerkelijk beginnen met het opslaan van de gebruikersdata. Door Mongoose is dit heel erg makkelijk.

app.post('/register', checkNotAuthenticated, async (req, res) => {
  try {
    const hashedPassword = await bcrypt.hash(req.body.password, 10)

    //This is the required data that has to be stored in mongodb
    const blog = new Blog({
      name: req.body.name,
      email: req.body.email,
      password: hashedPassword
    })
    
    //Saves user account data in mongodb, password is still secure and encrypted
    blog.save()
      .then((result) => {
        console.log(result)
      })
      .catch((err) => {
        console.log(error)
      })

    res.redirect('/login')
  } catch {
    res.redirect('/register')
  }
})

Om informatie op te slaan in Mongodb gebruik je in dit geval new Schema() omdat je bij het registreren een nieuw account aanmaakt. Dit zet je in een variabele, in dit geval const blog. Daarna zet je elke veld in dit Schema en geef je aan welke waarden daarbij hoort doormiddel van een req.body. Als dit gedaan is wil je dat de blog wordt opgeslagen door middel van .save(). Omdat dit een promise is moet je wel nog een .then gebruiken om zo de resultaten op te halen. Hierna kon ik de gebruiker doorsturen naar de login pagina.

Schermafbeelding 2022-06-23 om 12 43 56 De nieuw aangemaakte data komt na het refreshen direct op de admin pagina van Mongodb te staan.

Opslaan van vacatures in de database

Na het bespreken met Thierno wilde ik ook de vacatures in een database zetten. Dit werkte eigenlijk precies hetzelfde als bij het blog model. Alleen moest ik een formulier gebruiken die speciaal op de /developer route staat. Hierin kon ik op exact dezelfde wijze de vacatures opslaan.


const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const jobOfferSchema = new Schema({
    title: {
        type: String,
        required: true
    },
    location: {
        type: String,
        required: true
    },
    businessSectors: {
        type: String,
        required: true
    },
    introduction: {
        type: String,
        required: false
    },
    description: {
        type: String,
        required: false
    },
    responsibilities: {
        type: String,
        required: true
    },
    profile: {
        type: String,
        required: true
    },
    workingConditions: {
        type: String,
        required: true
    },
})

const Offers = mongoose.model('Offers', jobOfferSchema);
module.exports = Offers;

Dit is het datamodel voor de vacatures

app.post('/developer', (req, res) => {

  const offer = new Offers({
    title: req.body.title,
    location: req.body.location,
    businessSectors: req.body.businessSectors,
    introduction: req.body.introduction,
    description: req.body.description,
    responsibilities: req.body.responsibilities,
    profile: req.body.profile,
    workingConditions: req.body.workingConditions
  });

  offer.save()
  .then((result) => {
    console.log(result)
  })
  .catch((err) => {
    console.log(err)
  })

  res.render('developer');
})

Dit is de code voor het opslaan van de vacatures

Schermafbeelding 2022-06-23 om 12 49 52 Dit is de admin pagina voor het toevoegen van vacatures

Renderen van de vacatures

Ik vond het renderen van de vacatures nog best wel moeilijk. Dit omdat ik door middel van een loop door alle data moest heen gaan om het zo te renderen. Door de codereview heb ik dit uiteindelijk kunnen oplossen. Eerst werd de data alleen in een console.log() gezet. Om de data op te halen moest er eerst op de indexpagina de collectie worden opgehaald zodat dit gerenderd kan worden.

const Offers = require('./models/joboffers.js');

app.get('/', checkAuthenticated, async (req, res, next) => {

    Offers.find((err, docs) => {
      if(!err) {
        res.render('index', {
          data: docs,
          name: req.user.name
        })
      } else {
        console.log('Failed to retrieve data')
      }
    })
})

In de app.get van de indexpagina moet de collectie Offers worden opgehaald. Omdat ik alle bestanden wilde ophalen kon ik de .find() methode gebruiken om zo alle beschikbare data op te halen. Er wordt nu gezocht naar documenten en eventuele errors. Om te kijken of het ophalen van de data goed gaat heb ik een if statement geschreven die checkt of er een foutmelding is. Zo niet? Dan wordt index gerenderd en wordt de data in een object meegegeven. Is er wel een foutmelding, dan wordt deze in de console.log beschreven. Nu is alleen de data opgehaald. Ik moest een manier bedenken hoe ik deze data wilden gaan renderen op de indexpagina. Na de codereview werd mij dit helemaal duidelijk. Ik moest in de ejs een for loop schrijven die door alle data heen gaat en door middel van [i] de juiste data rendert.

     <% if(data.length) { %>
         <div class="containerVacatures">
             <ul id="vacatures">
                 <% for(let i = 0; i < data.length; i++) { %>
                 <section>
                     <li class="vacatures-li">
                         <h2> <%= data[i].title %> </h2>
                         <p> <%= data[i].introduction %> </p>
                           <ul>
                               <li> <%= data[i].location %></li>
                               <li> <%= data[i].businessSectors %></li>
                           </ul>
                           <p> <%= data[i].study %> </p>
                           <h3>Kernwoorden</h3>
                           <ul>
                               <li><%= data[i].keyword1 %></li>
                               <li><%= data[i].keyword2 %></li>
                               <li><%= data[i].keyword3 %></li>
                           </ul>
                         <button><a href="/<%= data[i].id %>"> Meer lezen...</a></button>
                     </li>
                 </section>
                 <% } %>
             </ul>
             <% } else { %>
                <p id="geen-resultaat">
                    Wij hebben helaas geen vacature gevonden met deze zoekopdracht.
                    Probeer het alsjeblieft opnieuw!
                </p>
                <% } %>

Dit is de detailpagina

  <main>
        <h1 id="titel">Details vacature</h1>
        <section id="vacature">
            <h1><%= data.title %> </h1>
            <article id='detail'>
                <p><%= data.introduction %></p>
                <ul id="data">
                    <li><%= data.location %></li>
                    <li><%= data.businessSectors %></li>
                </ul>
                <article>
                    <h2>Functie omschrijving</h2>
                    <p>
                        <%= data.description %>
                    </p>
                </article>
                <article>
                    <h2>Verantwoordelijkheden</h2>
                    <p>
                        <%= data.responsibilities %>
                    </p>
                </article>
                <article>
                    <h2>Werkomstandigheden</h2>
                    <p>
                        <%= data.workingConditions %>
                    </p>
                    <article>
                        <h2>Wie ben jij?</h2>
                        <p>
                            <%= data.study %>
                        </p>
                </article>
                <article>
                    <h2>Kernwoorden</h2>
                    <ul>
                        <li><%= data.keyword1 %></li>
                        <li><%= data.keyword2 %></li>
                        <li><%= data.keyword3 %></li>
                    </ul>
                </article>
                    <section id="container">
                        <form method="POST">
                            <button type="submit" id="favoriet">
                                <svg xmlns="http://www.w3.org/2000/svg" width="43" height="40" viewBox="0 0 43 40" id="favoriet">
                                    <path id="Icon_feather-heart" data-name="Icon feather-heart" d="M39.239,7.768a10.105,10.105,0,0,0-14.888,0L22.323,9.92,20.294,7.768a10.108,10.108,0,0,0-14.888,0,11.648,11.648,0,0,0,0,15.789l2.028,2.151L22.323,41.5,37.21,25.708l2.028-2.151a11.645,11.645,0,0,0,0-15.789Z" transform="translate(-0.823 -2.997)" fill="#2b9348" stroke="#2b9348" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/>
                                  </svg>  
                            </button>
                            </form>                    
                                          
                        <button id="apply">Soliciteren</button>                  
                    </section>
            </article>        
        </section>

Om onnodige errors te voorkomen moest er eerst een if statement worden geschreven die controleert of de data uberhaupt bestaat. Dit gebeurt door .length. Is dit niet zo? Dan krijgt de gebruiker een melding te zien dat er geen vacatures zijn ingeladen. In dit geval is vrijwel altijd de data opgehaald. Als de data opgehaald is kan ik door middel van een for loop door alle data heen gaan. Hierbij kan nu alle data een voor een worden ingeladen in hetzelfde formaat. Ik heb veel hulp gehad aan Nora die mij dit even goed heeft uitgelegd waardoor ik dit nu ook zelf kan schrijven.

Week drie was een super succesvolle en interessante week. Dit was mijn eerste keer ooit dat ik met een database heb gewerkt en vond het best prima gaan. Dit was een van de punten die ik bij de meesterproef goed onder de knie wil krijgen, dus dit is al een stap in de juiste richting.

Wat ging goed?

  • Kennismaken met Mongodb en Mongoose
  • Opzetten van Mongodb account en database
  • Connectie maken met Mongodb database
  • Opstellen van data models (Schema's)

Wat ging minder goed?

  • Iets te lang vastgezeten op het leren van lokaal Mongodb draaien (overgecompliceerd)
  • Ik had moeite om de opgehaalde data te renderen in de ejs

Week 4

In week 4 heb ik onnodig verschrikkelijk lang vast gezeten op een nieuw bestaand probleem. Dit heeft mij ruim een week gekost. Het probleem dat ik zelf had gecreëerd is dat ik extra velden in het user data model wilde toevoegen. Ik dacht dat ik gewoon het Schema bestand zou kunnen aanpassen dit zou werken. Toen ik dit checkte ben ik naar de admin pagina van Mongodb gegaan en zag ik deze nieuwe velden niet staan. Hierdoor ben ik dagen opzoek geweest op het internet naar mijn probleem om te kijken of anderen hetzelfde probleem hadden. Echter bleek dit probleem helemaal niet te bestaan. Als er een nieuw dataobject werd toegevoegd kwamen deze waardes er wel gewoon in te staan... Super stom dus, maar hier heb ik wel veel van geleerd. Hierdoor ben ik ook daadwerkelijk de waarde gaan inzien van Monoogse, omdat inderdaad te interactie heel subtiel en makkelijk verloopt.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

//This is how the structure of the data is going to look like
const blogSchema = new Schema({
    name: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true
    },
    password: {
        type: String,
        required: true
    },
    favorites: {
        type: [String],
        required: false
    },
    dominant: {
        type: Number,
        required: false
    },
    interactive: {
        type: Number,
        required: false
    },
    stable: {
        type: Number,
        required: false
    },
    conscientious: {
        type: Number,
        required: false
    }
}, { timestamps: true });

//Blog is user

const Blog = mongoose.model('Blog', blogSchema);
module.exports = Blog;

Vernieuwde data model

Naast het repareren van een niet bestaand probleem heb ik in het laatste gedeelte van deze week wel nog heel veel kunnen doen.

Vacatures opnieuw erin zetten

De vacatures die momenteel worden opgehaald zijn zonder kernwoorden. Deze kernwoorden zijn essentieel voor de resultaten van de disc test en het is dan ook belangrijk dat deze door middel van de database worden gekoppeld aan de vacatures. Na een week vast te hebben gezeten wist ik gelukkig nu wel hoe het moest en kon ik vrij eenvoudig het model aanpassen naar behoren.


const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const jobOfferSchema = new Schema({
    title: {
        type: String,
        required: true
    },
    location: {
        type: String,
        required: true
    },
    businessSectors: {
        type: String,
        required: true
    },
    introduction: {
        type: String,
        required: false
    },
    description: {
        type: String,
        required: false
    },
    responsibilities: {
        type: String,
        required: true
    },
    profile: {
        type: String,
        required: true
    },
    workingConditions: {
        type: String,
        required: true
    },
    study: {
        type: String,
        required: false
    },
    score: {
        type: String,
        required: true
    },
    keyword1: {
        type: String,
        required: false
    },
    keyword2: {
        type: String,
        required: false
    },
    keyword3: {
        type: String,
        required: false
    }
})

const Offers = mongoose.model('Offers', jobOfferSchema);
module.exports = Offers;

Dit is het vernieuwde data model van de vacatures

Helaas moest ik wel alle vacatures weer handmatig erin zetten wat een beetje een zuur werkje was. Na een uurtje druk kopiëren en plakken was dit gelukkig gedaan.

Vacature opslaan functie

Om de gebruiker nog meer interactie met de data te geven leek het mij een goed idee om het mogelijk te maken om vacatures op te slaan. In het gebruikers data model had ik al een veld gemaakt die favorites heet. Dit is een lege array. De bedoeling was dat bij het opslaan het objectId van de vacature werd meegegeven en deze in de array wordt opgehaald. Vervolgens moet op de profiel pagina de resultaten worden gerenderd.

app.post('/:id', checkAuthenticated, (req, res) => {
  const user = req.user.id;
  const offerId = req.params.id;
  
  Blog.findOneAndUpdate({
    _id: user
  }, {
    $push: {
      favorites: offerId
    }
  })
  .then((result) => {
    console.log(result)
    res.redirect('profile')
  })
})

In de app.post wordt het user id opgehaald en het object id van de vacature. Zo kon ik door middel van Blog.findOneAndUpdate gemakkelijk het juiste persoon vinden en dan door middel van $push het offerId in de array pushen.

app.get('/profile', checkAuthenticated, (req, res, next) => {
  const user = req.user.id;
  Blog.findById(user).then(results => {

    const allResults = results.favorites.map(element => {
      return Offers.findById(element).exec();
    });

    Promise.all(allResults).then(data => {
      // console.log(allResults)
      res.render('profile', {
        data: data,
        name: req.user.name
      })
    })
    
  })
  .catch((err) => {
    // console.log(err);
  })
})
 <% if(data.length) { %>
                <ul id="vacatures">
                    <% for(let i = 0; i < data.length; i++) { %>
                    <section>
                        <li class="vacatures-li">
                            <h2> <%= data[i].title %> </h2>
                            <p> <%= data[i].introduction %> </p>
                              <ul>
                                  <li> <%= data[i].location %></li>
                                  <li> <%= data[i].businessSectors %></li>
                              </ul>
                              <p> <%= data[i].study %> </p>
                              <h3>Kernwoorden</h3>
                              <ul>
                                  <li><%= data[i].keyword1 %></li>
                                  <li><%= data[i].keyword2 %></li>
                                  <li><%= data[i].keyword3 %></li>
                              </ul>
                              <form method="POST" action="/profile">
                                  <button name="delete" type="submit" value="<%= data[i].id %>">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="30.849" height="30.848" viewBox="0 0 30.849 30.848">
                                        <path id="Icon_metro-cross" data-name="Icon metro-cross" d="M33.138,26.711h0l-9.358-9.358,9.358-9.358h0a.966.966,0,0,0,0-1.363L28.717,2.21a.967.967,0,0,0-1.363,0h0L18,11.568,8.637,2.21h0a.966.966,0,0,0-1.363,0L2.852,6.631a.966.966,0,0,0,0,1.363h0l9.358,9.358L2.852,26.711h0a.966.966,0,0,0,0,1.363l4.421,4.421a.966.966,0,0,0,1.363,0h0L18,23.136l9.358,9.358h0a.966.966,0,0,0,1.363,0l4.421-4.421a.966.966,0,0,0,0-1.363Z" transform="translate(-2.571 -1.928)" fill="#fa3333"/>
                                      </svg>                                      
                                  </button>
                              </form>
                            <button><a href="<%= data[i].id %>"> Meer lezen...</a></button>
                        </li>
                    </section>
                    <% } %>
                </ul>
            <% } else { %>
                <p id="geen-resultaat">
                    Je hebt momenteel nog geen vacatures opgeslagen. Doe de DISC test of
                    zoek zelf naar jouw perfecte baan!
                    <a href="/">Browse vacatures</a>
                </p>

Om de opgeslagen vacatures te renderen moeten deze weer in de app.get van de profielpagina worden opgehaald. Hierbij heeft Robert mij heel erg geholpen. Ik kreeg namelijk een foutmelding over het sturen van de headers. Uiteindelijk bleek het dat ik for each verkeerd had gebruikt waardoor de pagina drie keer ging renderen en de informatie niet mee kon geven. door de data te filteren met .map en uiteindelijk ze allemaal een promise te geven door Promise.all kon de data juist worden opgehaald en worden gerenderd. Dit is met dan aan Robert, ik was totaal niet bekend met Promise.all. Super handig!

Week vier was een vermoeiende maar ook productieve week. Ik voel mij nog steeds super stom over het toevoegen van een nieuw veld in mongoose en heb hier naar mijn mening veel tijd op verspilt. Alles wat ik daarna heb gemaakt ben ik gelukkig wel super trots op, omdat de gebruiker steeds meer interactie kan hebben met de website

Wat ging goed?

  • Favoriete functie toegevoegd

Wat ging minder goed?

  • Ophalen van favoriet gemarkeerde vacatures
  • Mongodb extra velden in bestaand model toevoegen

Week 5

In week vijf kreeg ik aardig de smaak te pakken. Ik had nog een paar dingen op mijn lijstje die ik en zowel Koop en de opdrachtgever nog graag zouden willen zien. Ik ben dan ook hard aan het werk gegaan en flink wat avonden tot laat op geweest om dit allemaal nog toe te voegen. Dit is gelukkig ook gelukt en ik beschouw week vijf als een van mijn meest productieve weken ooit.

To do lijstje voor week 5

  • Favoriete vacature kunnen verwijderen
  • User model updaten voor DISC resultaten
  • Zoekfunctie van Joep toevoegen
  • Vacatures een score van D, I, S of C geven
  • Renderen van gerelateerde vacatures op basis van DISC resultaat

Favoriete vacature verwijderen functie

Als je een vacature kan toevoegen aan je favoriete lijst moet er natuurlijk ook de mogelijkheid zijn om deze te verwijderen. Dit was vrijwel heel snel gelukt na mij even ingelezen te hebben over verwijderen van items via Mongoose. Ik moest alleen even onderscheid maken in het verwijderen van een geheel object, of van een waarde binnenin dat project.

app.post('/profile', checkAuthenticated, async (req, res) => {
  const user = req.user.id;
  const objectId = req.body.delete;
  
  await Blog.findByIdAndUpdate(user, {
    $pull: {
      favorites: objectId
    }
  })
  res.redirect('profile')
})

In de app.post van de profielpagina wordt allereerst het user id opgehaald zodat het juiste user model kan worden gevonden. Ook moet het object id worden opgehaald van de functie. Deze wordt meegegeven in de value van de submit button van de vacature. Hier liep ik eerst tegen aan, maar kon met deze oplossing het juiste objectId vinden. Eenmaal beide id's gevonden kon ik heel gemakkelijk door Blog..findOneAndUpdate(user, ) de content verwijderen die ik wilde. Hiervoor moest ik $pull gebruiken en dan favorites koppelen aan objectId zodat het juiste id in de array zou worden verwijdert.

Updaten van user model voor disc resultaten

Om de resultaten van de DISC test aan de gebruiker te koppelen moest ik voor elke letter uit DISC een veld in het data model toevoegen die gelijk staat aan een Number

    dominant: {
        type: Number,
        required: false
    },
    interactive: {
        type: Number,
        required: false
    },
    stable: {
        type: Number,
        required: false
    },
    conscientious: {
        type: Number,
        required: false
    }

Structuur die moest worden toegevoegd

DISC test toevoegen (ook server side)

Een van de belangrijkste punten was de DISC test. Deze had Joep al opgesteld maar moest nog server side worden gerenderd.

    <form action="/disc" method="POST" enctype="application/x-www-form-urlencoded" required>
    
              <section>
                    <h2>Geef aan wat het beste bij jou past</h2>
                    <input type="radio" name="vraag1"  class="dominant" id="dominant" value="dominant" ><span>Direct</span><br>
                    <input type="radio" name="vraag1"  class="interactive" id="interactive" value="interactive" ><span>Spraakzaam</span><br>
                    <input type="radio" name="vraag1"  class="stable" id="stable" value="stable" ><span>Harmonieus</span><br>
                    <input type="radio" name="vraag1"  class="conscientieus" id="conscientieus" value="conscientieus" ><span>Analytisch</span>
                </section>

Het formulier bestond uit verschillende groepen radiobuttons met dezelfde name atribuut. Daarbij kregen ze wel een verschillende value zodat we in Node.js deze waardes kunnen verwerken.

app.post('/disc', checkAuthenticated, (req, res) => {
  //declaring the point system
  let dpoints = 0;
  let ipoints = 0;
  let spoints = 0;
  let cpoints = 0;

  const intro1 = req.body.intro1;
  if(intro1 === 'direct') {
    dpoints++;
    ipoints++
  } else if(intro1 === 'indirect') {
    spoints++;
    cpoints++;
  }

  const intro2 = req.body.intro2;
  if(intro1 === 'mensgericht') {
    dpoints++;
    ipoints++
  } else if(intro2 === 'taakgericht') {
    spoints++;
    cpoints++;
  }
  

  const question1 = req.body.vraag1;
  if(question1 === 'dominant') {
    dpoints++
  } else if(question1 === 'interactive') {
    ipoints++
  } else if(question1 === 'stable') {
    spoints++
  } else if(question1 === 'conscientieus') {
    cpoints++
  }

  const question2 = req.body.vraag2;
  if(question2 === 'dominant') {
    dpoints++
  } else if(question2 === 'interactive') {
    ipoints++
  } else if(question2 === 'stable') {
    spoints++
  } else if(question2 === 'conscientieus') {
    cpoints++
  }

  const question3 = req.body.vraag3;
  if(question3 === 'dominant') {
    dpoints++
  } else if(question3 === 'interactive') {
    ipoints++
  } else if(question3 === 'stable') {
    spoints++
  } else if(question3 === 'conscientieus') {
    cpoints++
  }

  const question4 = req.body.vraag3;
  if(question4 === 'dominant') {
    dpoints++
  } else if(question4 === 'interactive') {
    ipoints++
  } else if(question4 === 'stable') {
    spoints++
  } else if(question4 === 'conscientieus') {
    cpoints++
  }

  const question5 = req.body.karakter5;
  if(question5 === 'dominant') {
    dpoints++
  } else if(question5 === 'interactive') {
    ipoints++
  } else if(question5 === 'stable') {
    spoints++
  } else if(question5 === 'conscientieus') {
    cpoints++
  }

  const question6 = req.body.karakter6;
  if(question6 === 'dominant') {
    dpoints++
  } else if(question6 === 'interactive') {
    ipoints++
  } else if(question6 === 'stable') {
    spoints++
  } else if(question6 === 'conscientieus') {
    cpoints++
  }

  const question7 = req.body.karakter7;
  if(question7 === 'dominant') {
    dpoints++
  } else if(question7 === 'interactive') {
    ipoints++
  } else if(question7 === 'stable') {
    spoints++
  } else if(question7 === 'conscientieus') {
    cpoints++
  }

  const question8 = req.body.karakter8;
  if(question8 === 'dominant') {
    dpoints++
  } else if(question8 === 'interactive') {
    ipoints++
  } else if(question8 === 'stable') {
    spoints++
  } else if(question8 === 'conscientieus') {
    cpoints++
  }

  console.log('d points:' + dpoints)
  console.log('i points:' + ipoints)
  console.log('s points:' + spoints)
  console.log('c points:' + cpoints)
  
  if(dpoints > ipoints ||  dpoints > spoints || dpoints > cpoints) {
      Offers.find({score: "dominant"})
      .then((dombo) => {
        console.log(dombo)
        res.render('results', {data: dombo})
      })
    }
      else if(ipoints > dpoints ||  ipoints > spoints || ipoints > cpoints) {
        Offers.find({score: "interactief"})
        .then(dombo => {
          console.log(dombo)
          res.render('results', {data: dombo})
        })
        } else if(spoints > dpoints ||  ipoints > spoints || cpoints > cpoints){
          Offers.find({score: "stabiel"})
          .then(dombo => {
            console.log(dombo)
            res.render('results', {data: dombo})
          })
        } else if(cpoints > dpoints ||  ipoints > spoints || spoints > cpoints) {
            Offers.find({score: "conscientieus"})
            .then(dombo => {
              console.log(dombo)
              res.render('results', {data: dombo})
            })
  }

In de app.post van de disc pagina heb ik verschillende variabelen aangemaakt gebaseerd op D, I, S of C. Door middel van verschillende if statements konden zo de punten bij een bepaald antwoord juist worden toegevoegd aan de juiste variabelen. Na het verwerken van deze waarden worden deze waarden met elkaar vergeleken in een ander if statement. Dit if statement checkt als het ware wat de hoogste functie is en renderd dan de juiste data die de meest hoge punten heeft.

    <main>
    <h2 id="titelV">Vacatures op basis van jouw DISC resultaten</h2>
        <div class="containerVacatures">
            <ul id="vacatures">
                <% for(let i = 0; i < data.length; i++) { %>
                <section>
                    <li class="vacatures-li">
                        <h2> <%= data[i].title %> </h2>
                        <p> <%= data[i].introduction %> </p>
                          <ul>
                              <li> <%= data[i].location %></li>
                              <li> <%= data[i].businessSectors %></li>
                          </ul>
                          <p> <%= data[i].study %> </p>
                          <h3>Kernwoorden</h3>
                          <ul>
                              <li><%= data[i].keyword1 %></li>
                              <li><%= data[i].keyword2 %></li>
                              <li><%= data[i].keyword3 %></li>
                          </ul>
                        <button><a href="/<%= data[i].id %>"> Meer lezen...</a></button>
                    </li>
                </section>
            </ul>
                <% } %>
        </main>

Uiteindelijk wordt door middel van een for loop alle juiste data gerenderd op basis van de DISC test uitslag.

Zoekfunctie

Naast het invullen van de test moet het ook mogelijk zijn om zelf te zoeken naar een vacature. Joep had deze code al client side uitgewerkt. Dit moest echter lichtelijk worden aangepast omdat de data dit keer uit een database wordt opgehaald en niet via tekstt content. Tevens is .textContent() niet mogelijk via Node.js.

app.post('/', checkAuthenticated, async (req, res) => {
  const input = req.body.search;
  console.log(input);
  let search = await Offers.find({title: {$regex: new RegExp('^' + input + '.*', 'i')}}).exec()
   .then(response => {
     console.log(response)
     res.render('index', {data: response, name: req.user.name})
   })
})

In de app.post van de indexpagina wordt de input opgehaald door middel van req.body.search. Waarbij search de naam is van het zoekveld. Om te zoeken moest er gezocht worden in de database. Om dit makkelijker te maken heb ik gebruikt gemaakt van Regex, die er eigenlijk op een gemakkelijke wijze de input met de tekst uit de database vergelijkt. Door .exec wordt dit uiteindelijk uitgevoerd en kunnen met met de .then de results ophalen en dit renderen in de indexpagina. Wat super fijn was is dat automatisch de eerder gerenderde vacatures worden weggehaald.

Vacatures een DISC waarde meegeven

Om vacatures aan de DISC test resultaten te koppelen moesten de vacatures nog een bepaalde waarde meekrijgen. Deze kan gelijk staan aan d, i, s of c.

 score: {
        type: String,
        required: true
    },

Dit moest worden toegevoegd in het data model.

Vacatures koppelen aan DISC test resultaat

Om te checken wat de uiteindelijke score is moet er bepaald worden welke van de letters er het hoogst scoort. Voor de hoogst scorende waarde moeten de gekoppeld vacatures worden gerenderd.

 if(dpoints > ipoints ||  dpoints > spoints || dpoints > cpoints) {
      Offers.find({score: "dominant"})
      .then((dombo) => {
        console.log(dombo)
        res.render('results', {data: dombo})
      })
    }
      else if(ipoints > dpoints ||  ipoints > spoints || ipoints > cpoints) {
        Offers.find({score: "interactief"})
        .then(dombo => {
          console.log(dombo)
          res.render('results', {data: dombo})
        })
        } else if(spoints > dpoints ||  ipoints > spoints || cpoints > cpoints){
          Offers.find({score: "stabiel"})
          .then(dombo => {
            console.log(dombo)
            res.render('results', {data: dombo})
          })
        } else if(cpoints > dpoints ||  ipoints > spoints || spoints > cpoints) {
            Offers.find({score: "conscientieus"})
            .then(dombo => {
              console.log(dombo)
              res.render('results', {data: dombo})
            })
  }

Om te kijken naar de hoogste score waren er verschillende if statements nodig die controleren of het variabele hoger is dan de ander. Als dit het geval is, dan moet er in de collectie Offers gezocht worden naar het score veld en de waarde die gelijk staat aan deze variabele. Als bijvoorbeeld dominant het hoogst is, dan wordt door middel van Offers.find({score: 'dominant'}) gezocht naar alle vacatures die een score van dominant hebben. Vervolgens wordt de results pagina gerenderd en wordt de data in object formaat meegegeven

Wat ging goed?

  • Mijn motivatie en productiviteit lagen super hoog
  • Alle functionaliteiten kunnen toevoegen
  • Vrijwel zonder hulp veel problemen en bugs kunnen oplossen

Wat ging minder goed?

  • Ik heb deze week geen aanmerkingen over het functioneren, alles liep perfect

Reflectie

Web app from scratch

  • Wat ik meegenomen heb van WAFS is het ophalen, manipuleren en omzetten van data. In plaats van een API heb ik het nu uit een database opgehaald. Echter is het daarna renderen met .then en result nog steeds via dezelfde manier en heb ik dit kunnen toepassen.

CSS to the Rescue

Ik heb geen CSS geschreven dit project en heb daarom dit vak niet toegepast. Wel raad ik iedereen aan om zo min mogelijk en als het dan toch moet de id's duidelijke namen te geven.

Progressive Web app

  • Wat ik heb meegenomen is dat het efficienter is om je web app via de server in te laden. Dit zorgt voor een snelere reactie tijd en zorgt voor zo min mogelijk interactie tussen de server en de client. Tevens is de website nu nog bruikbaar. Ondanks dat de gebruiker de Javascript zou uitzetten.
  • Ik had wel graag nog gebruik willen maken van een service worker

Browser Technologies

  • Wat ik heb meegenomen is het opstellen van een semantisch formulier zodat altijd iedereen deze kan invullen
  • Doordat de html semantisch is en de pagina's server side worden gerenderd is de website ook als de Javascript en CSS worden uitgezet nog steeds volledig bruikbaar.

Human Centered Design

  • Wat ik heb meegenomen is dat je de gebruikers eisen goed in kaart moet hebben. Hierdoor kon ik de focus leggen op functionaliteiten die essentieel waren.

Real Time Web

  • Ik heb realtime web niet toegepast in dit project.
⚠️ **GitHub.com Fallback** ⚠️