---
# System prepended metadata

title: Tut5

---

```javascript=
import express, { json, Request, Response } from 'express';
import cors from 'cors';
import morgan from 'morgan';
import { port, url } from './config.json';
import {
  clear,
  peopleAdd,
  personView,
  personEdit,
  peopleList,
  personRemove,
  peopleStats,
} from './people';

const app = express();

// Use middleware that allows for access from other domains (needed for frontend to connect)
app.use(cors());
// Use middleware that allows us to access the JSON body of requests
app.use(json());
// Use middleware to log (print to terminal) incoming HTTP requests (OPTIONAL)
app.use(morgan('dev'));

app.get('/', (req: Request, res: Response) => {
  res.json({ message: 'Welcome to the root URL of names ages!' });
});

// TODO TUTOR 0: Clear
app.get('/clear', (req: Request, res: Response) => {
    res.json(clear()); 
});

// TODO GROUP 1: Add a person 

// POST request - therefore data is in the request BODY
app.post('/people/add', (req: Request, res: Response) => {
    const name = req.body.name as string;
    const age = parseInt(req.body.age);
    
    // Call peopleAdd - then check for errors
    const resultFromCall = peopleAdd(name, age);
    
    // If an error object is returned - send a 400 status code (or whatever the spec requires)
    if ('error' in resultFromCall) { // checks for key 'error' in return obj
        return res.status(400).json(resultFromCall);
    }
    
    // otherwise simply send response as is
    res.json(resultFromCall);
});



// TODO GROUP 2: Read a person's details

// GET request - therefore data is in request query! (Option 1)
app.get('/people/view', (req: Request, res: Response) => {
    const personId = parseInt(req.query.personId);
    
    // Call personView - then check for errors
    const resultFromCall = personView(personId);
    
    // If an error object is returned - set a 400 status code (or whatever the spec requires)
    if ('error' in resultFromCall) { // checks for key 'error' in return obj
        return res.status(400).json(resultFromCall);
    }
    res.json(resultFromCall);
});

// GET request - can put id in path param (Option 2)
app.get('/people/view/:id', (req: Request, res: Response) => {
     const personId = parseInt(req.params.id); // must make sure the params.<name> matches name in url (ie. /person/view/:<name>)
    
    // Call personView - then check for errors
    const resultFromCall = personView(personId);
    
    // If an error object is returned - set a 400 status code (or whatever the spec requires)
    if ('error' in resultFromCall) { // checks for key 'error' in return obj
        return res.status(400).json(resultFromCall);
    }
    res.json(resultFromCall);
});



// TODO GROUP 3: List all people

// GET request - most likely will put the data inside query - path params are more for ids etc.
app.get('/people/list', (req: Request, res: Response) => {
    const minAge = parseInt(req.query.minAge);
    
    // Call peoepleList - then check for errors
    const resultFromCall = peopleList(minAge);
    
    // If an error object is returned - set a 400 status code (or whatever the spec requires)
    if ('error' in resultFromCall) { // checks for key 'error' in return obj
        return res.status(400).json(resultFromCall);
    }
    res.json(resultFromCall);
});    



// TODO GROUP 4: Update a person

// PUT request - data goes in the body! Can also put in the id in the path so why not
app.put('/people/:id/update', (req: Request, res: Response) => {
    const personId = parseInt(req.params.id);
    const name = req.body.name as string;
    const age = parseInt(req.body.age);
    
    // Call personUpdate - then check for errors
    const resultFromCall = personUpdate(personId, name, age);
    
    // If an error object is returned - set a 400 status code (or whatever the spec requires)
    if ('error' in resultFromCall) { // checks for key 'error' in return obj
        return res.status(400).json(resultFromCall);
    }
    res.json(resultFromCall);
});



// TODO GROUP 5: Remove a person

// DELETE request - data goes in the query (or path but probably query in this case)
app.delete('/people/remove', (req: Request, res: Response) => {
    const personId = parseInt(req.query.personId);
    
    // Call personRemove - then check for errors
    const resultFromCall = personRemove(minAge);
    
    // If an error object is returned - set a 400 status code (or whatever the spec requires)
    if ('error' in resultFromCall) { // checks for key 'error' in return obj
        return res.status(400).json(resultFromCall);
    }
    res.json(resultFromCall);
});



app.listen(port, () => {
  console.log(`Express Server started and awaiting requests at the URL: '${url}:${port}'`);
});

```

Implementations of functions in people.ts
```javascript=
import { Person, getData } from './dataStore';

const findPerson = (personId: number): Person | undefined => {
  // NOTE: requires { "lib": ["ES2015"] } in tsconfig.json
  return getData().people.find(person => person.personId === personId);
};

const findPersonByName = (name: string): Person | undefined => {
  // NOTE: requires { "lib": ["ES2015"] } in tsconfig.json
  return getData().people.find(person => person.name === name);
};

export function clear() {
  getData().people = [];
  return {};
}

export function peopleAdd(name: string, age: number) {
  const data = getData();
  if (!name || findPersonByName(name) || age <= 0) {
    return { error: 'Name is empty, name already exists or age is not strictly positive.' };
  }
  const person = { name, age, personId: data.numPeopleCreated };
  data.people.push(person);
  data.numPeopleCreated += 1;
  return { personId: person.personId };
}

export function peopleList(minAge: number) {
  const data = getData();
  if (minAge < 0) {
    return { error: 'minAge must be non-negative' };
  }
  const people = data.people
    .filter(p => p.age >= minAge)
    .sort((p1, p2) => p2.age - p1.age || p1.name.localeCompare(p2.name));
  return { people };
}

export function personView(personId: number) {
  const person = findPerson(personId);
  if (person === undefined) {
    return { error: 'no such person with given id ' };
  }
  return { person };
}

export function personEdit(personId: number, name: string, age: number) {
  const person = findPerson(personId);
  if (person === undefined) {
    return { error: 'name is empty or age is not strictly positive' };
  }
  if (!name || age <= 0 || (name !== person.name && findPersonByName(name))) {
    return { error: 'name is empty, age is not strictly positive or name is already taken' };
  }
  person.name = name;
  person.age = age;
  return {};
}

export function personRemove(personId: number) {
  if (!findPerson(personId)) {
    return { error: 'person with given id does not exist' };
  }
  const data = getData();
  data.people = getData().people.filter(person => person.personId !== personId);
  return {};
}

export function peopleStats() {
  const data = getData();
  if (data.people.length === 0) {
    return { error: 'no entries available in the data store' };
  }
  return {
    stats: {
      minAge: Math.min(...data.people.map(nameAge => nameAge.age)),
      maxAge: Math.max(...data.people.map(nameAge => nameAge.age)),
      averageAge: data.people.reduce((sum, nameAge) => sum + nameAge.age, 0) / data.people.length,
    }
  };
}

// CRUD