text:
# Convert your Raspberry into a VPN
If you want to be able to access your homelab from the outside of your network without the need of opening your network to the world and dealing with all the security issues that this means.

Turning your Raspberry into a VPN is pretty easy thanks to the folks developing [PiVPN](https://www.pivpn.io/). The simplest way to setup and manage a VPN, designed for Raspberry Pi.  But as everything in these docs, it also works with any kind of server like Ubuntu Server.

If you are running a Raspberry, it is recommended to run Raspbian Lite.

I had an old Raspberry Pi 3b+ at home which was unused for ages. I will be using this RPI3 as my PiVPN and I will also setup a second PiHole as a backup of the main one.

To install [PiVPN](https://www.pivpn.io/) you just need to run:

- `curl -L https://install.pivpn.io | bash`

This will guide you though a step-by-step process which will help you configure your PiVPN. I strongly recommend you to use Wireguard as it is **much** more efficient than OpenVPN.

Once you have set up your PiVPN don't forget to forward the port you set up (default is `51820`) from your router to your PiVPN local IP. You can do this from your router configuration, probably under a section called `Port Forwarding`.

Your configuration should look like somtething like this:

| Name | Type | Port Start | Port End | NAT interface | Private IP | Private Port |
| --- | --- | --- | --- | --- | --- | --- |
| Wireguard | Port-Range | 51820 | 51820 | TPC or UDP (Both) | eth0.vxxx | 192.168.1.223 |

## Add a new device

To add a new device is quite easy, just run `pivpn add` on your PiVPN server, give it a name, and you are good to go.

## Connect to PiVPN

Once you have created your PiVPN client profile (using `pivpn add`) you can connect form various devices.

Install the Wireguard client on your machine (Android app, iOS app, Linux CLI client, etc). If you are on a desktop machine, import your configuration following [the PiVPN guide](https://github.com/pivpn/pivpn/wiki/WireGuard#importing-profiles-on-client-machines), which should be pretty easy.

If you are on Android or iOS you can just run `pivpn -qr`, then select the client profile and scan the QR code from the Wireguard app. That's it, connected!

mime_type: text/plain


text:
# Set up the RPI4 server

Download Ubuntu Server image or Raspbian for Raspberry Pi. It is recommended to get the LTS version.

- Ubuntu Server: https://ubuntu.com/download/raspberry-pi
- Raspbian: https://www.raspberrypi.org/software/operating-systems/

Raspbian is more lightweight and more suitable for Raspberry.

Burn the image on the SD card using [**Balena Etcher**](https://www.balena.io/etcher/) (or [Rufus](https://rufus.ie/en_US/)).

Insert the SD card to the Raspberry and connect your Raspberry to a power supply so it will boot. You should connect your Raspberry to some kind of display using the HDMI ports for the first configurations (at least until SSH is ready).

Once booted, log in using the default credentials for your server:

- User: `ubuntu` Password: `ubuntu` if we are on **Ubuntu Server**.
- User: `pi` Password: `raspberry` if we are on **Raspbian**.

Once logged in, change the password. It should automatically ask you to change it. If not, use `passwd` to change it.

Depending on what OS you chose, you can jump to:
- [Raspbian config](https://github.com/pluja/Selfhosted-Google-Photos-Alternative/blob/main/01-Setting-Up-The-Server.md#raspbian-config)
- [Ubuntu config](https://github.com/pluja/Selfhosted-Google-Photos-Alternative/blob/main/01-Setting-Up-The-Server.md#ubuntu-config)

## Raspbian config

> The `sudo raspi-config` command comes very handy for basic configuration. Use it to enable SSH server and change keyboard layout before proceding.

Now we will configure the internet connections for the RPI. We will be setting up the Wi-Fi connection with DHCP4 (will assign an IP dynamically; this is for the first updates and so) and then we will be configuring the Ethernet port with a static IP for when we set up the RPI as a server.

To setup the WiFi connection use `sudo raspi-config` and search through the menus for the WiFi configuration. There, enter the SSID (network name) and Password to connect to your network. Once done, reboot the RPI.

Now update all packages with:
`sudo apt-get update && sudo apt-get upgrade`

Now let's configure the static IP for the `eth0` port.

For this we will need to edit the `/etc/dhcpcd.conf` file:
- `sudo nano /etc/dhcpcd.conf`

Add the following lines at the end of the file:

```
interface eth0
static ip_address=192.168.1.222/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1 9.9.9.9
```

This way we are assigning the static ip `192.168.1.222` to the raspberry pi `eth0` port. Also we are using `9.9.9.9` as the DNS provider (Quad9). You can change this to suit your needs.

Now shut down the RPI with `shutdown now` and connect the Ethernet cable. Once booted, try to SSH to the RPI-4 with the assigned IP:

`ssh username@ip`

Once you have `ssh` to the RPI, you can proceed to secure the SSH configuration. To do so we will deactivate the password and root login for SSH for security.

To generate a private+public key pair on * NIX systems, launch this command on the local machine (your workstation, not the server):

`ssh-keygen -t ed25519 -C "comment" `

You need to specify the path for the keys when you choose the keys filename. You can use the default folder destination for the keys. Then you will need to copy the generated keys to the remote machine (RPI):

`ssh-copy-id -i $HOME/.ssh/keyname.pub username@ip`

Now you can try to `ssh` again to the server and see that you are no longer prompted for a password:

`ssh username@server`

> NOTE: make sure to back up your ssh keys (`$HOME/.ssh/` directory)

Now we will edit the SSH config to make it more secure:

`sudo nano /etc/ssh/sshd_config`

You will need to find the following variables and set the values to the ones you can see here. This will disable the root login and the password prompt. You will **only** be able to login to your server via SSH with a pair of valid keys.

```
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no
PermitRootLogin no
```

Once done, restart the SSH service:

`sudo systemctl reload ssh`

And finally, install a basic firewall protection:

`sudo apt install fail2ban`

Fail2ban will ban the IPs that try to intrude your system, it will ban them after 5 tries with an exponentially increasing time ban.

Activate Fail2Ban:

`sudo systemctl start fail2ban && sudo systemctl enable fail2ban`

If you want to check the jailed IPs use:

`fail2ban-client status`

It is recommended to further configure it [with this guide](https://linuxhandbook.com/fail2ban-basic/) if you wish to learn more about fail2ban.

You are done with setting up your server. Now you can proceed to the second step of the guide.

## Ubuntu config

Now we will configure the internet connections for the RPI. We will be setting up the Wi-Fi connection with DHCP4 (will assign an IP dynamically; this is for the first updates and so) and then we will be configuring the Ethernet port with a static IP for when we set up the RPI-4 as a server.

To do so we will need to edit the file `50-cloud-init.yaml`

- `sudo vim /etc/netplan/50-cloud-init.yaml`

Once we are editing it we will paste this configuration (replacing whatever is in the file):

```
network:
    version: 2
    ethernets:
        eth0:
          dhcp4: no
          addresses:
            - 192.168.1.221/24
          gateway4: 192.168.1.1
          nameservers:
            addresses: [9.9.9.9, 1.1.1.1]
    wifis:
        wlan0:
            optional: true
            access-points:
                "<SSID>":
                     password: "<YourW1f1PassW0rD"
            dhcp4: true
```

Now we will save using `ESC` and then the command `:wq` to exit and save Vim. Make sure to change anything that is between `<>` to suit your network. Also check that the `gateway4` (router) is the same for your network. And assign the IP of your choice: with this config the RPI-4 IP will be `192.168.1.221`.

Once done, apply this config with `sudo netplan apply`.

Now we will configure SSH so we can control the RPI from another computer; and we will also deactivate the password and root login for SSH for security.

To generate a private+public key pair on *NIX systems, launch this command on the local machine (not RPI):

`ssh-keygen -t ed25519 -C "raspberrypi" `

You can use the default folder destination for the keys. Then you will need to copy the generated keys to the remote machine (RPI):

`ssh-copy-id -i $HOME/.ssh/id_rsa.pub ubuntu@192.168.1.221`

Now you are ready to connect via SSH to the RPI:

`ssh ubuntu@192.168.1.221`

Now we will edit the SSH config to make it more secure:

`sudo vim /etc/ssh/sshd_config`

You will need to find the following variables and set the values to the ones you can see here:

```
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no
PermitRootLogin no
```

Once done, restart the ssh service:

`sudo systemctl reload ssh`

And finally, install a basic firewall protection:

`sudo apt install fail2ban`

Fail2ban will ban the IPs that try to intrude your system, it will ban them after 5 tries with an exponentially increasing time ban.

Activate Fail2Ban:

`sudo systemctl start fail2ban && sudo systemctl enable fail2ban`

If you want to check the jailed IPs use:

`fail2ban-client status`

It is recommended to further configure it (with this guide)[https://linuxhandbook.com/fail2ban-basic/] if you wish to learn more about fail2ban.

You are done with setting up your server. Now you can proceed to the second step of the guide.


text:
# Replacing Google Photos

To replace google photos I will be using a combination of programs and scripts that will end by giving me the very same functionallity I got when I was using Google Photos.

These technologies will be:

- [PiGallery2](https://github.com/bpatrik/pigallery2) - A simple and lightweight gallery with all that you expect. Read only.
  - You can also use [PhotoView](https://github.com/photoview/photoview) instead if you want to be able to do the Face Recognition on the Raspberry itself. I personally prefer not to carry this workload on the RPI.
- [Syncthing](https://syncthing.net/) - A simple, fast, peer-to-peer syncing tool.
- [Phockup](https://github.com/ivandokov/phockup) - Script to organize your Photos.
- [Czkawka](https://github.com/qarmin/czkawka) - Find duplicates for your photos really fast.
- [SSHFS](https://github.com/libfuse/sshfs) - Manage remote Gallery folder locally.
- [DigiKam](https://www.digikam.org/) - Face Recognition and Metadata management.
- rsync - Efficient backup tool.
- rclone - Rsync for cloud services.

I will be synching my Phone photos using Syncthing to my Raspberry Pi 4 server. There I will run a serires of scripts to help me organize the photos by Year and Month. Then I will make use of DigiKam locally to have the ability to run Face tagging and recogntion along with metadata tweaking.

Then, once done, I will encrypt and backup everything to MEGA using `rclone` so I have an online and encrypted backup of everything. I will also keep a local copy of everything using `rsync` on a separate HDD that will automatically turn on when needed. Let's go.

## Preparation

### Folder Structure

I will have a 3TB HDD connected to my Raspberry Pi where I will be synching all my photos. This HDD is automatically mounted on the `/data/3TB` folder on boot. To do this automation, connect your Storage device on the RPI and run the following command:

`sudo blkid`

You will get a list where you should find your storage device listed:

```shell
/dev/sdb1: LABEL="3TB" UUID="16955984-34c8-435d-b646-6578a3bab016" TYPE="ext4" PARTUUID="ba10c453-2297-5f49-9886-fd76517576cd"
```

You will need to copy the **UUID** label. Then edit the fstab:

- `sudo nano /etc/fstab`

And add the following line usign the UUID and mountpoint for your drive and system:

`UUID=16955984-34c8-435d-b646-6578a3bab016 /data/3Tera auto nosuid,nodev,nofail 0 0`

Save it. Now your External storage will be mounted on boot.

Inside my 3TB storage I have a folder called Photos, just for organization. You can skip this folder if you don't think it is useful. I created it because i may be using the storage for other kinds of media like Music or Films.

Now, inside this folder I have the `tmp` folder for pigallery2 and the `images` folder. Inside images I have two more folders the first one is `Imports` which contains two more folders: `Phone` and `PC`. Here is where all the images will be uploaded from my devices without any kind of Sorting or deduplication. Then I have the `Gallery` folder, here is where all my photos will be kept sorted and perfectly ordered.

The folder structure insidte the 3 TB drive looks like this:

```
Photos/
├── tmp/
└── images/
    ├── Imports/
    │   ├── Phone/
    │   └── PC/
    └── Gallery/
```

## Installing programs

First we need to install the main programs that are needed for everything to work.

### Rust:

Rust is a secure compiled programming language which allows for very efficient applications. We will use it for deleting exact-image duplicates on our Gallery.

```bash
sudo apt install build-essential
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```

### Syncthing

Syncthing is a very powerful open-source synchronization tool:
> Syncthing is a continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how it's transmitted over the internet. - From [Syncthing.net](https://syncthing.net)

On your raspberry server run:

- `sudo apt install syncthing`

Now you have installed syncthing. On a web browser on any computer on your network visit `192.168.1.222:8384` (if you changed the static IP for your server previously use the one you chose).

[Install Syncthing on all the devices you want to connect](https://syncthing.net/downloads/)

It is important that you read [this 'Getting Started' page](https://docs.syncthing.net/intro/getting-started.html) if you have never used Syncthing before. Using this guide connect your devices (for example your Phone and your Raspberry server) and understand how all works. I also recommend you to [read this page](https://docs.syncthing.net/intro/gui.html) to get introduced to Syncthing GUI. It is important that you understand how it works as you don't want to mess things up as you could end up losing your files somehow.

In the following section we will configure the folder synchronization between hosts.

#### Syncthing Folders

As we have seen before, I will show you how I configured the above folder structure with syncthing so I can sync my photo sources (Phone and computer). If you have more sources you can just add more folders.

##### Phone Camera

This folder must sync one way. I want to be able to delete the photos from my phone without losing them on my server too. This is why we will set up IgnoreDelete and Send-Only folders.

- *SERVER* Destination Folder: /data/images/Imports/Phone

- *PHONE* Source Folder: /DCIM/Camera

The *PHONE* folder will be of type **Send-Only** and the *SERVER* folder will be **Recieve-Only.**

It is also very important that we set the **IgnoreDelete** setting to **True** on the server for our Phone folder so we can free up space on the Phone by deleting the photos without them getting delete on the server too. This can be done from the **Syncthing Advanced Folders settings** (See the following image to find out where this option resides):
![](https://i.imgur.com/Bv5eD4B.png)

##### Computer Folder

If we have enough space on our computer, we can save a copy of the **Sorted** folder we will have on our Server so we can move and create new Folders and Albums. We can use **Digikam** from the computer in order to organize all the photos locally and then propagate these changes to the server using Syncthing.

*SERVER*: /data/images/Gallery

*COMPUTER*: /Pictures/Gallery

We will set both folders as Send-Recieve, we may want to have the "IgnoreDelete" form the PC to the Server just in case, but this is optional.

### PiGallery2

This will be the app we will be using to display our Gallery folder. Installing PiGallery2 on our Raspberry is pretty easy, you just need to follow the [app instructions on their README.md](https://github.com/bpatrik/pigallery2/blob/master/docker/README.md). I really recommend using Docker-Compose.

---

Now we have our Syncronization method set up. It was quite easy, we will have our photos from our Phone synced to our server in a matter of minutes and in a peer-to-peer way. How nice and how private!

## The Scripts

Now we will work on different scripts. The IMPORT script, that will handle the sorting, de-duplication and deletion of files and the BACKUP that, as the name already clarifies, will handle the cloud-backup of our photos to any cloud-provider of our choice.

Let's go. I want to have my photos sorted by Year/MM-Month folder structure (eg. 2021/01-January) automatically. This will let me have a very organized gallery from the very beggining. To do this I will be using a very nice script called [Phockup](https://github.com/ivandokov/phockup). This scripts allows us to do exactly what we want to do. To install it we will use the following commands on our server:

```bash
sudo apt-get install python3 libimage-exiftool-perl -y
curl -L https://github.com/ivandokov/phockup/archive/latest.tar.gz -o phockup.tar.gz
tar -zxf phockup.tar.gz
sudo mv phockup-* /opt/phockup
sudo ln -s /opt/phockup/phockup.py /usr/local/bin/phockup
```

Now we have installed Phockup. The command I will be using to Sort my folder will be the following:

`phockup /source/folder /destination/folder --move --date YYYY/MM-M --date-field=FileModifyDate`

This command will take the contents of the source folder and **move** them to the destination folder. If you want to keep a copy of the original uploaded files without being sorted, you can remove the `--move` flag. The `--date YYYY/MM-M` specifies the folder structure we want in this case YEAR/XX-Month. we also have an additional flag called `date-field` that specifies the date field from exiftool that needs to be taken if there is no date specified.

Now we need to install Czkawka in order to achieve the de-duplication of exact same images, videos, etc. To do this we will need to have previously correctly installed Rust and be able to make use of the **cargo** command. Once done, we will:

```bash
git clone https://github.com/qarmin/czkawka.git
cd czkawka
cargo run --release --bin czkawka_cli
```

This will compile Czkawka CLI version so we can use it.

The script for the Import of the files will be the following:

```bash
#!/bin/bash
sourceFolder="/data/3Tera/photos/Imports"
sortedFolder="/data/3Tera/photos/Gallery"
if [ `ls $sourceFolder/Phone $sourceFolder/PC | wc -l` -gt 0 ]
then
        phockup $sourceFolder $sortedFolder --move --date YYYY/MM-M --date-field=FileModifyDate
        ~/czkawka/target/release/czkawka_cli dup -d $sortedFolder -x IMAGE VIDEO -s hashmb -f results-dup.txt --delete-method AEO
fi
```

I am sure you can come up with a better script, if you do don't doubt opening an issue and I will replace it. As I am lazy, and for my setup this is OK I won't be thinking for a better syntax for this. Maybe you could just

This script lets us have imports from both the Computer and the Phone in separate folders to avoid conflicts and strange synchronization issues. You might be able to use a unique folder on the server but I prefer using it this way.

It checks if the Imports directory is empty. If not, then it does the import. If is empty it does nothing.

We need to create a cronjob to run this script every hour (for example) to check if there are new imports, if there are we will sort them and move them to our Gallery and then remove any duplicates. To do this open your crontab with

- `crontab -e`

And paste this (change the IMPORT.sh script path) for running the IMPORT script every 15 minutes.

```
*/15 * * * * /home/pi/IMPORT.sh
```

I run it every 15 minutes as I don't take too much photos along a day (I take ~10 photos a day). If you take a lot of photos you may want to do it more regularly. Or if you are not interested on having your photos in your gallery almost instantly, you can just do it once a day, for example overnight.

### Remote Folder on local machine: Using Digikam for Metadata completion and Face Recognition

You can mount the remote Gallery folder on your local machine so you can work with it. To do it you can make use of `SSH FILESYSTEM` which allows you to mount a remote filesystem to your machine and use it as if it was a regular folder.

This will also allow us to set up DigiKam wiht this mounted folder as a Local Collection and then, for example, scan the collection for faces. Using this method we can keep our Raspberry free of the heavy-workload that face recoginition means and derive this work on our local machine. The only drawback is that we will need to manually run the face recognition from DigiKam.

If you have a **large collection** of photos, I recommend you to get your photos on an SSD on your local machine and scan your collection for faces from there. Once scanned, tagged and written to the metadata upload them to the raspberry pi (or use that same SSD there) as this will make the process much faster.

For me, I started a new gallery from the ground up this year. My old (2TB) photos are kept in an old HDD but I don't need them on my daily gallery for now.

I scanned over 1000 full-quality photos from `sshfs` on the folder mounted on the RPI 3TB HDD in about 25 minutes. I assume that this same process on a local SSD would have taken half the time.

If you want to set the Face Tags and other useful metadata (or edit metada), or just edit the remote Gallery from your desktop computer You need to install `sshfs`:

- `sudo apt install sshfs`

And then mount the remote folder to a local folder like this:

- `sshfs pi@192.168.1.222:/data/3Tera/images/Gallery /home/pluja/Pictures/PiGallery -o idmap=user -o ui d=1000 -o gid=1000`

You can [Download DigiKam AppImage](https://www.digikam.org/download/). DigiKam is an awesome photo management tool. You can do many things there like creating albums or editing metadata, but the most interesting one is the face detection and recognition. Using this you can scan your gallery for faces. You will be able to tag some faces and once done, you can recognize them based on the already tagged faces:

![](https://i.imgur.com/8hqpRlE.jpeg)

Now go to **Settings > Configure DigiKam** and navigate to the **Metadata** section. In the first section on the **Behaviour** tab called "Write this information to the Metadata" select the information you want. If you are tagging faces, select the **Face Tags** so this information is written to the files.

After you have tagged the faces, select a face (for example "Albert Einstein" in the photo) and now you can go to **Album > Write Metadata To Files** to write face Metadata to the files so PiGallery2 can read it.

If you want to unmount the folder, you can use:

- `fusermount -u /home/pluja/Pictures/PiGallery`

## Backing up everything

I want to backup everything and have several copies for all. I want a local copy on a separate HDD which will automatically turn on at night with a timer switch and an additional encrypted copy to be uploaded to a Cloud service like MEGA.

For now, I will just use the local backup and in the near future I will update this file with how to backup to a cloud service. For the cloud backups you can use **rclone** (the tool I will use in the future tutorial), you can investigate about it. In a few words, is just like **rsync** but for cloud services.

### Local Backup

I will be using a very simple yet efficient method for backing everything up. I won't do any compression or packing of files. I will just sync the folders using `rsync`. This command lets you have incremental copies for a folder to a remote host (or locally). As I have a second Raspberry Pi, I will be backing up to a HDD connected to my second server for increased backup security.

The `rsync` command reads a source folder and only writes the differences to the destination folder. The first time it will copy everything, so if you have a big gallery it will take a considerable ammount of time depending on if you are using an HDD or SSD or if you are copying to a remote host. But after that first upload, every backup will only upload the files that have changed or that are new. So it will be much more efficient and fast than copying the whole folder every time.

Here is the BACKUP.sh file:

```bash
#!/bin/bash
sourceFolder = /data/3Tera/pigallery2/images/Gallery
destinationFolder = pi@192.168.1.223:/data/backup/backup/
logPath = /data/3Tera
(echo "------`date`------" && rsync -ai $sourceFolder $destinationFolder) &>> $backupPath/backup.log
```

This will generate a `backup.log` file at the `logPath` destination. It will print the date of the backup and then thanks to the `-i` flag it will only write the newly backed up files. The `-a` flag will copy the Gallery folder structure as is and will keep all files properties among other things. You should definitely use the `-a` flag.

As we said, after the first time, it will only backup the new and the changed images so for this reason you can make the backup more regularly. For example every 3 or 6 hours. Edit you crontab with `crontab -e` to suit your needs. I used the following syntax:

```
20 7-23/4 * * * /home/pi/BACKUP.sh
```

This will make a backup *at minute 30 past every 4th hour from 7 through 23*. I make it start at the 45th minute so it does not conflict with the IMPORT.sh script that takes place every 15 minutes.


text:
##
 Replacing Google Photos

#
 Self-Hosted Photo Storage Solution

After testing a lot (almost all of them) of alternatives and solutions. Finally I came up with the definitive solution, the one that gives me everything I want in an easy (once set up is done), cheap, secure and lightweight way.

At the end of this tutorial we will have a Photo Storage solution that will allow us to:

- [x] Sync our photos from any device.
- [x] Remove exact-photo duplicates efficiently.
- [x] Automatically organize our Gallery by Year and Month.
- [x] Manually run Face Recognition and Face Scanning.
- [x] Manage our gallery from DigiKam.
- [x] Have an online gallery with a map, faces, tags, ratings and other filters.
- [x] Upload an encrypted backup of our gallery to any cloud service.
- [x] Locally generate a backup on a separated drive.
- [x] Securelly access our Gallery from anywhere outside our network.

This can be achieved using a Raspberry Pi 4 and a HDD Storage drive. Pretty cheap!

The stack we will be using is the following:

- [PiGallery2](https://github.com/bpatrik/pigallery2) - A simple and lightweight gallery with all that you expect. Read only.
  - You can also use [PhotoView](https://github.com/photoview/photoview) instead if you want to be able to do the Face Recognition on the Raspberry itself. I personally prefer not to carry this workload on the RPI.
- [Syncthing](https://syncthing.net/) - A simple, fast, peer-to-peer syncing tool.
- [Phockup](https://github.com/ivandokov/phockup) - Script to organize your Photos.
- [Czkawka](https://github.com/qarmin/czkawka) - Find duplicates for your photos really fast.
- [SSHFS](https://github.com/libfuse/sshfs) - Manage remote Gallery folder locally.
- [DigiKam](https://www.digikam.org/) - Face Recognition and Metadata management.
- rsync - Efficient backup tool.
- rclone - Rsync for cloud services.

## The Guide

1. [Set up the server](https://github.com/pluja/simple-selfhosting/blob/main/01-Setting-Up-The-Server.md#setup-rpi4-server)
2. [Setting up the Google Photos replacement](https://github.com/pluja/simple-selfhosting/blob/main/02-Photo-Storage-Solution.md#replacing-google-photos)
3. [Outside access: PiVPN](https://github.com/pluja/simple-selfhosting/blob/main/03-PiVPN-Convert-Raspi-Into-VPN.md#convert-your-raspberry-into-a-vpn)


text:
## Heuristics for Vendoring MIT Licensed Code

Disclaimer: I am not a lawyer, but I've helped maintain a dependency manager for a decade and this is stuff that comes up.

> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

Is what you are trying to conform to in the MIT license.

Prefer video? [An overview of different code licenses](https://www.youtube.com/watch?v=0r7JcN3Q_LY) and how to use them in your code in a talk I gave at Artsy 2015. There's a few cool new licenses since then, but nothing you would be using in everyday code.

### So, you want to vendor some code.

There are roughly 5 ways in which you would do it. It doesn't matter if your code is OSS or closed-source, as the license still applies:

1. **Let your package manager do it for you**. You're always doing this all the time. This works because the package manager includes the license file or references it in the package manifest.

   Not all package managers ensure licensing info exists, so even then you may need to _manually_ verify the license is referenced in their code (I used to use [danger-plugin-yarn](https://github.com/orta/danger-plugin-yarn) to highlight this)

2. **You are copying a few lines of code** or a function. Add a reference comment going back to the original:

    ```ts
    // Originally taken from shikijs because it was not exported, I've sent a PR upstream
    // https://github.com/shikijs/shiki/blob/33ae6b97eda78484460ac05b3338bc72a605194a/packages/shiki/src/highlighter.ts#L210
    // License MIT: https://github.com/shikijs/shiki/blob/33ae6b97eda78484460ac05b3338bc72a605194a/LICENSE

    function isPlaintext(lang: string | null | undefined) {
        return !lang || ['plaintext', 'txt', 'text'].includes(lang)
    }
    ```

    > Note: this is what you're meant to do with code from StackOverflow.

    > Note 2: Referencing the git commit and the original LOC is optional, you **only** need the reference to the license. That said, licenses can change - so this is future-proofing.

    ```ts
    // Originally from shikijs https://github.com/shikijs/shiki/blob/main/LICENSE

    function isPlaintext(lang: string | null | undefined) {
        return !lang || ['plaintext', 'txt', 'text'].includes(lang)
    }
    ```

    Is totally OK.

3. **You are copying a whole file**. You can either:
   1. Add a reference comment going back to the original. For example in shikijs we have auto-updating themes based on other people's repos. What we do there is:

        ```json
        {
        "_copyright": "The MIT License (MIT)\nCopyright (c) 2015-2022 spgennard\nSource: https://github.com/spgennard/vscode_cobol/blob/main/syntaxes/COBOL.tmLanguage.json",
        "$schema": "https://raw.githubusercontent.com/spgennard/vscode_cobol/main/schemas/tmlanguage.json",
        "fileTypes": [
            "ccp",
            "scbl",
            "cobol",

        ```
    2. Make a folder which includes a full copy of the license, and that specific file:

        ```
        tree .

        vendor/COBOL
        ├── LICENSE
        └── COBOL.tmLanguage.json
        ```

      I'd recommend making sure that the license file contains a HTTP link to the original repo also, if it doesn't add a new file for it.

4. **You took some code but you changed it so much** that you just want to cover your butt. I did this for [RedwoodJS here](https://github.com/redwoodjs/redwood/blob/0e9754beaaee7fea21cb4f85027f49a41a679795/packages/web/src/components/DevFatalErrorPage.tsx) after taking a library's code, re-writing it to React, making it make sense for Redwood while still kept note of its origins and license:

    ```ts
    // This file is a hard fork of panic-overlay for RedwoodJS. The original code
    // is licensed under The Unlicense - https://github.com/xpl/panic-overlay/blob/master/LICENSE
    // making it fine for embedding inside this project.

    import { useState } from 'react'
    ```

5. **You have a substantial set of files, or are vendoring a whole project**. You want to put it in its own folder, ensure there is the original license and a link to the original codebase and what commit you took it from (I think you can technically argue that `git submodules` handles this referential part for you if you use that) but something like:

    Then it is totally enclosed, and obvious where this code has come from and who was the original authors:

    ```sh
    tree .

    vendor/shikijs-monochrome
    ├── LICENSE
    ├── index.ts
    └── README.md
    ```

    For MIT you _do not_ need to keep track of changes (some licenses do, in those cases use a repo fork with git submodules IMO) you just keep the original reference around.

### Things worth keeping in mind

The code you vendor is licensed at the time you import it. So, make sure to link to the commit at which you vendored the code if you think it may change. For example if v0 -> v8 are MIT and then v9 is GPL, then you can vendor (as MIT) any version prior to the v9 switch.

As the 'legality' here is about lawyer-work on interpreting how credit is given, there's a possible argument that you _dont_ even need to link to the license as we collectively agreed upon a [standardized notation](https://spdx.org/licenses/) for licenses ([SPDX](https://spdx.dev)) which means you can write `// MIT (author)` and that _should_ conform to the license for small usage. This works because there's no other "version" of the MIT license making it unambiguous, and because contextually we know that this comment is refering to the SPDX shortcut name.

I don't think that level of minimalism is too useful though.

repo_name: orta/Heuristics-for-vendoring-MIT-code


text:
# Homebrew-tiny-scripts

Homebrew tap to install scripts from [vitorgalvao](https://github.com/vitorgalvao/), notably [tiny-scripts](https://github.com/vitorgalvao/tiny-scripts).

## Usage

You’ll need [Homebrew](http://brew.sh/) installed to run these.

[Tap](https://github.com/Homebrew/brew/blob/master/docs/Taps.md) this repository by running:

```bash
brew tap vitorgalvao/tiny-scripts
```

Afterwards, install them as any other formula. For example, to install `ringtonemaker`, run:

```bash
brew install ringtonemaker
```

#### License

The Unlicense (Public Domain, essentially)

repo_name: vitorgalvao/homebrew-tiny-scripts

repo_language: Ruby

mime_type: text/x-ruby


text:
class Podbook < Formula
  desc "Generate a podcast RSS feed for audiobooks"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  depends_on "exiftool"
  depends_on "vitorgalvao/tiny-scripts/human-media-time"

  def install
    bin.install "podbook"
  end
end


text:
class PinboardBackup < Formula
  desc "Backup all bookmarks from a pinboard account"
  homepage "https://github.com/vitorgalvao/tiny-pinboard-scripts"
  url "https://github.com/vitorgalvao/tiny-pinboard-scripts.git"
  version "2022.1"

  def install
    bin.install "pinboard-backup"
  end
end


text:
class Gfv < Formula
  desc "Make animated gifs from a video file"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  depends_on "ffmpeg"
  depends_on "gifski" => :optional

  def install
    bin.install "gfv"
  end
end


text:
class SetupPhpContactForm < Formula
  desc "Setup for easy start of a PHP contact form"
  homepage "https://github.com/vitorgalvao/setup-php-contact-form"
  url "https://github.com/vitorgalvao/setup-php-contact-form.git"
  version "2022.1"

  def install
    bin.install "setup-php-contact-form"
  end
end


text:
class RssItemSort < Formula
  desc "Sort RSS items by number in title and upload the result to temporary hosting"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  def install
    bin.install "rss-item-sort"
  end
end


text:
class CaskAnalytics < Formula
  desc "Show analytics information for casks in the main taps"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  def install
    bin.install "cask-analytics"
  end
end


text:
class MtgWallpapers < Formula
  desc "Download Magic: The Gathering wallpapers"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.2"

  def install
    bin.install "mtg-wallpapers"
    man1.install "manpages/mtg-wallpapers.1"
  end
end


text:
class Pingpong < Formula
  desc "Stitch a video with its reversed version, for continuous loops"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  depends_on "ffmpeg"

  def install
    bin.install "pingpong"
  end
end


text:
class Makeicns < Formula
  desc "Make an icns file from a png"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  def install
    bin.install "makeicns"
  end
end


text:
class Ringtonemaker < Formula
  desc "Convert a video or audio file to an iPhone ringtone"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  depends_on "ffmpeg"

  def install
    bin.install "ringtonemaker"
  end
end


text:
class CaskRepair < Formula
  desc "Quickly repair outdated/broken Casks from homebrew-cask"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  depends_on "hub"
  depends_on "hr" => :recommended

  def install
    bin.install "cask-repair"
  end
end


text:
class Progressbar < Formula
  desc "Overlay a progress bar on videos or gifs"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  depends_on "ffmpeg"
  depends_on "gfv"
  depends_on "imagemagick"

  def install
    bin.install "progressbar"
    man1.install "manpages/progressbar.1"
  end
end


text:
class PkgExtract < Formula
  desc "Extract pkg files in a logical hierarchy"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.2"

  def install
    bin.install "pkg-extract"
  end
end


text:
class UploadFile < Formula
  desc "Upload paths, with support for several file hosts"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2023.3"

  def install
    bin.install "upload-file"
  end
end


text:
class PinboardDeleteUnread < Formula
  desc "Delete Pinboard unread bookmarks older than X days"
  homepage "https://github.com/vitorgalvao/tiny-pinboard-scripts"
  url "https://github.com/vitorgalvao/tiny-pinboard-scripts.git"
  version "2022.1"

  def install
    bin.install "pinboard-delete-unread"
  end
end


text:
class LinuxUsb < Formula
  desc "Facilitate installing Linux on a USB stick, from macOS"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  def install
    bin.install "linux-usb"
  end
end


text:
class LosslessCompress < Formula
  desc "Losslessly compress files"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  depends_on "gifsicle"
  depends_on "jpeg"
  depends_on "optipng"

  def install
    bin.install "lossless-compress"
  end
end


text:
class PinboardWaybackmachine < Formula
  desc "Add to the Wayback Machine links saved to a pinboard account"
  homepage "https://github.com/vitorgalvao/tiny-pinboard-scripts"
  url "https://github.com/vitorgalvao/tiny-pinboard-scripts.git"
  version "2023.1"

  def install
    bin.install "pinboard-waybackmachine"
  end
end


text:
class Tape < Formula
  desc "Backup and restore software settings on macOS"
  homepage "https://github.com/vitorgalvao/tape"
  url "https://github.com/vitorgalvao/tape.git", branch: "main"
  version "2022.4"

  depends_on "ruby"

  def install
    bin.install "tape"
  end
end


text:
class PinboardUrlUpdate < Formula
  desc "Substitute URLs of pinboard bookmarks"
  homepage "https://github.com/vitorgalvao/tiny-pinboard-scripts"
  url "https://github.com/vitorgalvao/tiny-pinboard-scripts.git"
  version "2022.1"

  def install
    bin.install "pinboard-url-update"
  end
end


text:
class AppIconExtract < Formula
  desc "Extract app bundle icon as png"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  def install
    bin.install "app-icon-extract"
  end
end


text:
class PinboardLinkCheck < Formula
  desc "Check the status code of links saved to a pinboard account"
  homepage "https://github.com/vitorgalvao/tiny-pinboard-scripts"
  url "https://github.com/vitorgalvao/tiny-pinboard-scripts.git"
  version "2022.1"

  def install
    bin.install "pinboard-link-check"
  end
end


text:
class Seren < Formula
  desc "Rename files in a numerical sequence"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  def install
    bin.install "seren"
  end
end


text:
class HumanMediaTime < Formula
  desc "Calculate and present total running time of media files in a human-readable way"
  homepage "https://github.com/vitorgalvao/tiny-scripts"
  url "https://github.com/vitorgalvao/tiny-scripts.git"
  version "2022.1"

  depends_on "media-info"

  def install
    bin.install "human-media-time"
  end
end


text:
# to-do-notifications

> **NOTE:** This repository has been archived and the content is now maintained as part of the [dom-examples](https://github.com/mdn/dom-examples/) repository.

Enhanced version of the to-do app, which stores to-do items via IndexedDB, and then also aims to provide notifications when to-do item deadlines are up, via the Notification and Vibration APIs.

repo_name: mdn/to-do-notifications

repo_language: JavaScript


text:
<!DOCTYPE HTML>

	<meta charset="UTF-8">
	<meta name="viewport" content="width=380">

	<title>To-do list with Notifications</title>

	<link rel="icon" type="image/png" href="img/icon-128.png">
    <link href='https://fonts.googleapis.com/css?family=Donegal+One|Lily+Script+One' rel='stylesheet' type='text/css'>
	<link href="style/style.css" type="text/css" rel="stylesheet">

To-do list

Add new to-do item.

<form id="task-form" action="index.html">

Task title:<input type="text" id="title" required>

Hours (hh):<input type="number" id="deadline-hours" required>

Mins (mm):<input type="number" id="deadline-minutes" required>

Day:
        <select id="deadline-day" required>
          <option value="01">01</option>
          <option value="02">02</option>
          <option value="03">03</option>
          <option value="04">04</option>
          <option value="05">05</option>
          <option value="06">06</option>
          <option value="07">07</option>
          <option value="08">08</option>
          <option value="09">09</option>
          <option value="10">10</option>
          <option value="11">11</option>
          <option value="12">12</option>
          <option value="13">13</option>
          <option value="14">14</option>
          <option value="15">15</option>
          <option value="16">16</option>
          <option value="17">17</option>
          <option value="18">18</option>
          <option value="19">19</option>
          <option value="20">20</option>
          <option value="21">21</option>
          <option value="22">22</option>
          <option value="23">23</option>
          <option value="24">24</option>
          <option value="25">25</option>
          <option value="26">26</option>
          <option value="27">27</option>
          <option value="28">28</option>
          <option value="29">29</option>
          <option value="30">30</option>
          <option value="31">31</option>
        </select>

Month:
       <select id="deadline-month" required>
         <option value="January">January</option>
         <option value="February">February</option>
         <option value="March">March</option>
         <option value="April">April</option>
         <option value="May">May</option>
         <option value="June">June</option>
         <option value="July">July</option>
         <option value="August">August</option>
         <option value="September">September</option>
         <option value="October">October</option>
         <option value="November">November</option>
         <option value="December">December</option>
       </select>

Year:
       <select id="deadline-year" required>
	 <option value="2025">2025</option>
         <option value="2024">2024</option>
         <option value="2023">2023</option>
         <option value="2022">2022</option>
         <option value="2021">2021</option>
         <option value="2020">2020</option>
         <option value="2019">2019</option>
         <option value="2018">2018</option>
       </select>

<input type="submit" id="submit" value="Add Task">

</form>

  <button id="enable">
    Enable notifications
  </button>


text:
to-do-notifications
===================

This is an Enhanced version of my basic to-do app, which stores to-do items via IndexedDB, and then also aims to provide notifications when to-do item deadlines are up, via the Notification and Vibration APIs.

The IndexedDB and Notification API functionality all works on Firefox desktop, Firefox Android, Firefox OS, Chrome, and IE 10+.

The Vibration API stuff works on Firefox OS and Firefox for Android.

You can [try it out live](https://mdn.github.io/to-do-notifications/).


text:
window.onload = () => {
  const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];

  // Hold an instance of a db object for us to store the IndexedDB data in
  let db;

  // Create a reference to the notifications list in the bottom of the app; we will write database messages into this list by
  // appending list items as children of this element
  const note = document.getElementById('notifications');

  // All other UI elements we need for the app
  const taskList = document.getElementById('task-list');
  const taskForm = document.getElementById('task-form');
  const title = document.getElementById('title');
  const hours = document.getElementById('deadline-hours');
  const minutes = document.getElementById('deadline-minutes');
  const day = document.getElementById('deadline-day');
  const month = document.getElementById('deadline-month');
  const year = document.getElementById('deadline-year');
  const notificationBtn = document.getElementById('enable');

  // Do an initial check to see what the notification permission state is
  if (Notification.permission === 'denied' || Notification.permission === 'default') {
    notificationBtn.style.display = 'block';
  } else {
    notificationBtn.style.display = 'none';
  }

  note.appendChild(createListItem('App initialised.'));

  // Let us open our database
  const DBOpenRequest = window.indexedDB.open('toDoList', 4);

  // Register two event handlers to act on the database being opened successfully, or not
  DBOpenRequest.onerror = (event) => {
    note.appendChild(createListItem('Error loading database.'));
  };

  DBOpenRequest.onsuccess = (event) => {
    note.appendChild(createListItem('Database initialised.'));

    // Store the result of opening the database in the db variable. This is used a lot below
    db = DBOpenRequest.result;

    // Run the displayData() function to populate the task list with all the to-do list data already in the IndexedDB
    displayData();
  };

  // This event handles the event whereby a new version of the database needs to be created
  // Either one has not been created before, or a new version number has been submitted via the
  // window.indexedDB.open line above
  //it is only implemented in recent browsers
  DBOpenRequest.onupgradeneeded = (event) => {
    db = event.target.result;

    db.onerror = (event) => {
      note.appendChild(createListItem('Error loading database.'));
    };

    // Create an objectStore for this database
    const objectStore = db.createObjectStore('toDoList', { keyPath: 'taskTitle' });

    // Define what data items the objectStore will contain
    objectStore.createIndex('hours', 'hours', { unique: false });
    objectStore.createIndex('minutes', 'minutes', { unique: false });
    objectStore.createIndex('day', 'day', { unique: false });
    objectStore.createIndex('month', 'month', { unique: false });
    objectStore.createIndex('year', 'year', { unique: false });

    objectStore.createIndex('notified', 'notified', { unique: false });

    note.appendChild(createListItem('Object store created.'));
  };

  function displayData() {
    // First clear the content of the task list so that you don't get a huge long list of duplicate stuff each time
    // the display is updated.
    while (taskList.firstChild) {
      taskList.removeChild(taskList.lastChild);
    }

    // Open our object store and then get a cursor list of all the different data items in the IDB to iterate through
    const objectStore = db.transaction('toDoList').objectStore('toDoList');
    objectStore.openCursor().onsuccess = (event) => {
      const cursor = event.target.result;
      // Check if there are no (more) cursor items to iterate through
      if (!cursor) {
        // No more items to iterate through, we quit.
        note.appendChild(createListItem('Entries all displayed.'));
        return;
      }

      // Check which suffix the deadline day of the month needs
      const { hours, minutes, day, month, year, notified, taskTitle } = cursor.value;
      const ordDay = ordinal(day);

      // Build the to-do list entry and put it into the list item.
      const toDoText = `${taskTitle} — ${hours}:${minutes}, ${month} ${ordDay} ${year}.`;
      const listItem = createListItem(toDoText);

      if (notified === 'yes') {
        listItem.style.textDecoration = 'line-through';
        listItem.style.color = 'rgba(255, 0, 0, 0.5)';
      }

      // Put the item item inside the task list
      taskList.appendChild(listItem);

      // Create a delete button inside each list item,
      const deleteButton = document.createElement('button');
      listItem.appendChild(deleteButton);
      deleteButton.textContent = 'X';

      // Set a data attribute on our delete button to associate the task it relates to.
      deleteButton.setAttribute('data-task', taskTitle);

      // Associate action (deletion) when clicked
      deleteButton.onclick = (event) => {
        deleteItem(event);
      };

      // continue on to the next item in the cursor
      cursor.continue();
    };
  };

  // Add listener for clicking the submit button
  taskForm.addEventListener('submit', addData, false);

  function addData(e) {
    // Prevent default, as we don't want the form to submit in the conventional way
    e.preventDefault();

    // Stop the form submitting if any values are left empty.
    // This should never happen as there is the required attribute
    if (title.value === '' || hours.value === null || minutes.value === null || day.value === '' || month.value === '' || year.value === null) {
      note.appendChild(createListItem('Data not submitted — form incomplete.'));
      return;
    }

    // Grab the values entered into the form fields and store them in an object ready for being inserted into the IndexedDB
    const newItem = [
      { taskTitle: title.value, hours: hours.value, minutes: minutes.value, day: day.value, month: month.value, year: year.value, notified: 'no' },
    ];

    // Open a read/write DB transaction, ready for adding the data
    const transaction = db.transaction(['toDoList'], 'readwrite');

    // Report on the success of the transaction completing, when everything is done
    transaction.oncomplete = () => {
      note.appendChild(createListItem('Transaction completed: database modification finished.'));

      // Update the display of data to show the newly added item, by running displayData() again.
      displayData();
    };

    // Handler for any unexpected error
    transaction.onerror = () => {
      note.appendChild(createListItem(`Transaction not opened due to error: ${transaction.error}`));
    };

    // Call an object store that's already been added to the database
    const objectStore = transaction.objectStore('toDoList');
    console.log(objectStore.indexNames);
    console.log(objectStore.keyPath);
    console.log(objectStore.name);
    console.log(objectStore.transaction);
    console.log(objectStore.autoIncrement);

    // Make a request to add our newItem object to the object store
    const objectStoreRequest = objectStore.add(newItem[0]);
    objectStoreRequest.onsuccess = (event) => {

      // Report the success of our request
      // (to detect whether it has been succesfully
      // added to the database, you'd look at transaction.oncomplete)
      note.appendChild(createListItem('Request successful.'));

      // Clear the form, ready for adding the next entry
      title.value = '';
      hours.value = null;
      minutes.value = null;
      day.value = 01;
      month.value = 'January';
      year.value = 2020;
    };
  };

  function deleteItem(event) {
    // Retrieve the name of the task we want to delete
    const dataTask = event.target.getAttribute('data-task');

    // Open a database transaction and delete the task, finding it by the name we retrieved above
    const transaction = db.transaction(['toDoList'], 'readwrite');
    transaction.objectStore('toDoList').delete(dataTask);

    // Report that the data item has been deleted
    transaction.oncomplete = () => {
      // Delete the parent of the button, which is the list item, so it is no longer displayed
      event.target.parentNode.parentNode.removeChild(event.target.parentNode);
      note.appendChild(createListItem(`Task "${dataTask}" deleted.`));
    };
  };

  // Check whether the deadline for each task is up or not, and responds appropriately
  function checkDeadlines() {
    // First of all check whether notifications are enabled or denied
    if (Notification.permission === 'denied' || Notification.permission === 'default') {
      notificationBtn.style.display = 'block';
    } else {
      notificationBtn.style.display = 'none';
    }

    // Grab the current time and date
    const now = new Date();

    // From the now variable, store the current minutes, hours, day of the month, month, year and seconds
    const minuteCheck = now.getMinutes();
    const hourCheck = now.getHours();
    const dayCheck = now.getDate(); // Do not use getDay() that returns the day of the week, 1 to 7
    const monthCheck = now.getMonth();
    const yearCheck = now.getFullYear(); // Do not use getYear() that is deprecated.

    // Open a new transaction
    const objectStore = db.transaction(['toDoList'], 'readwrite').objectStore('toDoList');

    // Open a cursor to iterate through all the data items in the IndexedDB
    objectStore.openCursor().onsuccess = (event) => {
      const cursor = event.target.result;
      if (!cursor) return;
      const { hours, minutes, day, month, year, notified, taskTitle } = cursor.value;

      // convert the month names we have installed in the IDB into a month number that JavaScript will understand.
      // The JavaScript date object creates month values as a number between 0 and 11.
      const monthNumber = MONTHS.indexOf(month);
      if (monthNumber === -1) throw new Error('Incorrect month entered in database.');

      // Check if the current hours, minutes, day, month and year values match the stored values for each task.
      // The parseInt() function transforms the value from a string to a number for comparison
      // (taking care of leading zeros, and removing spaces and underscores from the string).
      let matched = parseInt(hours) === hourCheck;
      matched &&= parseInt(minutes) === minuteCheck;
      matched &&= parseInt(day) === dayCheck;
      matched &&= parseInt(monthNumber) === monthCheck;
      matched &&= parseInt(year) === yearCheck;
      if (matched && notified === 'no') {
        // If the numbers all do match, run the createNotification() function to create a system notification
        // but only if the permission is set
        if (Notification.permission === 'granted') {
          createNotification(taskTitle);
        }
      }

      // Move on to the next cursor item
      cursor.continue();
    };
  };

  // Ask for permission when the 'Enable notifications' button is clicked
  function askNotificationPermission() {
    // Function to actually ask the permissions
    function handlePermission(permission) {
      // Whatever the user answers, we make sure Chrome stores the information
      if (!Reflect.has(Notification, 'permission')) {
        Notification.permission = permission;
      }

      // Set the button to shown or hidden, depending on what the user answers
      if (Notification.permission === 'denied' || Notification.permission === 'default') {
        notificationBtn.style.display = 'block';
      } else {
        notificationBtn.style.display = 'none';
      }
    };

    // Check if the browser supports notifications
    if (!Reflect.has(window, 'Notification')) {
      console.log('This browser does not support notifications.');
    } else {
      if (checkNotificationPromise()) {
        Notification.requestPermission().then(handlePermission);
      } else {
        Notification.requestPermission(handlePermission);
      }
    }
  };

  // Check whether browser supports the promise version of requestPermission()
  // Safari only supports the old callback-based version
  function checkNotificationPromise() {
    try {
      Notification.requestPermission().then();
    } catch(e) {
      return false;
    }

    return true;
  };

  // Wire up notification permission functionality to 'Enable notifications' button
  notificationBtn.addEventListener('click', askNotificationPermission);

  function createListItem(contents) {
    const listItem = document.createElement('li');
    listItem.textContent = contents;
    return listItem;
  };

  // Create a notification with the given title
  function createNotification(title) {
    // Create and show the notification
    const img = '/to-do-notifications/img/icon-128.png';
    const text = `HEY! Your task "${title}" is now overdue.`;
    const notification = new Notification('To do list', { body: text, icon: img });

    // We need to update the value of notified to 'yes' in this particular data object, so the
    // notification won't be set off on it again

    // First open up a transaction
    const objectStore = db.transaction(['toDoList'], 'readwrite').objectStore('toDoList');

    // Get the to-do list object that has this title as its title
    const objectStoreTitleRequest = objectStore.get(title);

    objectStoreTitleRequest.onsuccess = () => {
      // Grab the data object returned as the result
      const data = objectStoreTitleRequest.result;

      // Update the notified value in the object to 'yes'
      data.notified = 'yes';

      // Create another request that inserts the item back into the database
      const updateTitleRequest = objectStore.put(data);

      // When this new request succeeds, run the displayData() function again to update the display
      updateTitleRequest.onsuccess = () => {
        displayData();
      };
    };
  };

  // Using a setInterval to run the checkDeadlines() function every second
  setInterval(checkDeadlines, 1000);
}

// Helper function returning the day of the month followed by an ordinal (st, nd, or rd)
function ordinal(day) {
  const n = day.toString();
  const last = n.slice(-1);
  if (last === '1' && n !== '11') return `${n}st`;
  if (last === '2' && n !== '12') return `${n}nd`;
  if (last === '3' && n !== '13') return `${n}rd`;
  return `${n}th`;
};


text:
/* Basic set up + sizing for containers */

html,
body {
  margin: 0;
}

html {
  width: 100%;
  height: 100%;
  font-size: 10px;
  font-family: Georgia, "Times New Roman", Times, serif;
  background: #111;
}

body {
  width: 50rem;
  position: relative;
  background: #d88;
  margin: 0 auto;
  border-left: 2px solid #d33;
  border-right: 2px solid #d33;
}

h1,
h2 {
  text-align: center;
  background: #d88;
  font-family: Arial, Helvetica, sans-serif;
}

h1 {
  font-size: 6rem;
  margin: 0;
  background: #d66;
}

h2 {
  font-size: 2.4rem;
}

/* Bottom toolbar styling  */

#toolbar {
  position: relative;
  height: 6rem;
  width: 100%;
  background: #d66;
  border-top: 2px solid #d33;
  border-bottom: 2px solid #d33;
}

#enable,
input[type="submit"] {
  line-height: 1.8;
  font-size: 1.3rem;
  border-radius: 5px;
  border: 1px solid black;
  color: black;
  text-shadow: 1px 1px 1px black;
  border: 1px solid rgba(0, 0, 0, 0.1);
  box-shadow: inset 0px 5px 3px rgba(255, 255, 255, 0.2),
    inset 0px -5px 3px rgba(0, 0, 0, 0.2);
}

#enable {
  position: absolute;
  bottom: 0.3rem;
  right: 0.3rem;
}

#notifications {
  margin: 0;
  position: relative;
  padding: 0.3rem;
  background: #ddd;
  position: absolute;
  top: 0rem;
  left: 0rem;
  height: 5.4rem;
  width: 50%;
  overflow: auto;
  line-height: 1.2;
}

#notifications li {
  margin-left: 1.5rem;
}

/* New item form styling */

.form-box {
  background: #d66;
  width: 85%;
  padding: 1rem;
  margin: 2rem auto;
  box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.7);
}

form div {
  margin-bottom: 1rem;
}

form .full-width {
  margin: 1rem auto 2rem;
  width: 100%;
}

form .half-width {
  width: 50%;
  float: left;
}

form .third-width {
  width: 33%;
  float: left;
}

form div label {
  width: 10rem;
  float: left;
  padding-right: 1rem;
  font-size: 1.6rem;
  line-height: 1.6;
}

form .full-width input {
  width: 30rem;
}

form .half-width input {
  width: 8.75rem;
}

form .third-width select {
  width: 13.5rem;
}

form div input[type="submit"] {
  clear: both;
  width: 20rem;
  display: block;
  height: 3rem;
  margin: 0 auto;
  position: relative;
  top: 0.5rem;
}

/* || tasks box */

.task-box {
  width: 85%;
  padding: 1rem;
  margin: 2rem auto;
  font-size: 1.8rem;
}

.task-box ul {
  margin: 0;
  padding: 0;
}

.task-box li {
  list-style-type: none;
  padding: 1rem;
  border-bottom: 2px solid #d33;
}

.task-box li:last-child {
  border-bottom: none;
}

.task-box li:last-child {
  margin-bottom: 0rem;
}

.task-box button {
  margin-left: 2rem;
  font-size: 1.6rem;
  border: 1px solid #eee;
  border-radius: 5px;
  box-shadow: inset 0 -2px 5px rgba(0, 0, 0, 0.5) 1px 1px 1px black;
}

/* setting cursor for interactive controls */

button,
input[type="submit"],
select {
  cursor: pointer;
}

/* media query for small screens */

@media (max-width: 32rem) {
  body {
    width: 100%;
    border-left: none;
    border-right: none;
  }

  form div {
    clear: both;
  }

  form .full-width {
    margin: 1rem auto;
  }

  form .half-width {
    width: 100%;
    float: none;
  }

  form .third-width {
    width: 100%;
    float: none;
  }

  form div label {
    width: 36%;
    padding-left: 1rem;
  }

  form input,
  form select,
  form label {
    line-height: 2.5rem;
    font-size: 2rem;
  }

  form .full-width input {
    width: 50%;
  }

  form .half-width input {
    width: 50%;
  }

  form .third-width select {
    width: 50%;
  }

  #enable {
    right: 1rem;
  }
}


text:
# Como escrever?

Para escrever sua resposta deverá ser no formato Markdown, caso você não conheça **POR FAVOR LEIA** [markdowntutorial.com](http://www.markdowntutorial.com/), pois se você não enviar com a formatação correta para códigos eu não podderei aceitar seu exercício.

# [Grupo do Telegram](https://telegram.me/joinchat/BJ_gtz49o2OSzTU90SKp-A)

Para entrar no grupo oficial do Be mean no Telegram basta seguir o link [https://telegram.me/joinchat/BJ_gtz49o2OSzTU90SKp-A](https://telegram.me/joinchat/BJ_gtz49o2OSzTU90SKp-A), lembrando que só é permitido postagens e discussões sobre dúvidas e/ou assuntos pertinentes ao curso, aquele que não respeitar essas regras será banido.
## O Envio

Caso você faça um `pull` antes de fazer o seu `Pull Request` por favor **COMITE APENAS SEU ARQUIVO, NÃO FAÇA `git add .`!!**

**Faça:**
- `git add nome_do_meu_arquivo.md`
- `git commit -m 'SEU NOME - NodeJS - Exercício XX resolvido'`
- `git push origin master`

## Padrão do arquivo

Favor sempre usar: **class-X-resolved-githubuser-nome-completo-com-hifens.md**

    class-01-resolved-suissa-Jean-Carlo-Nascimento.md

**NENHUM ARQUIVO FORA DESSE PADRÃO SERÁ ACEITO!!!**

## Prazo

O prazo final é antes de entregar o **seu** projeto final, que também não tem um prazo final, pois o curso acontecerá sem um tempo definido devido aos futuros alunos.

## Obrigatório entregar **TODOS** antes do seu projeto final.


text:
# Node.js - Aula 04 - Exercício
**Autor:** Rafael Crispim Ignácio

**Data:** 1451444633263

## 1. Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma.

```js
function print(name, callback) {
    if (typeof name == "string") {
        return callback(null, name);
    } else {
        var err = new Error("Tipo inválido, você deve informar uma String.");
        return callback(err, null);
    }
}
print("Rafael", function(err, name) {
    if (err) throw err;
    console.log(name);
});
```

## 2. Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.

```js
function sum(x, y, callback) {
    if (typeof x == "number" && typeof y == "number") {
        return callback(null, (x + y));
    } else {
        var err = new Error("Tipo inválido, você deve informar somente números.");
        return callback(err, null);
    }
}
sum(10, 20, function(err, result){
    if (err) throw err;
    console.log("SUM Result: " + result);
});
```

## 3. Criar uma que calcular a média de dois valores e imprima essa média uma outra função, como continuação da execução da mesma.

```js
function avg(x, y, callback) {
    var err = null;
    if (typeof x != "number" || typeof y != "number") {
        err = new Error("Tipo inválido, você deve informar somente números.");
    } else if (y === 0) {
        err = new Error("O divisor não pode ser igual a 0.");
    }
    return (err) ? callback(err, null) : callback(null, (x / y));
}
avg(10, 20, function(err, result){
    if (err) throw err;
    console.log("AVG Result: " + result);
});
```

## 4. Explicar a definição de continuação de uma função.
Até onde eu entendi, a continuação de uma função pode ser definida como uma função que será executada a partir de outra, ou seja, é uma função passada como parâmetro(`callback`) em uma função principal e é chamada no momento de execução deste método.


text:
# Node.js - Aula 04 - Exercício
**user:** [tuchedsf](https://github.com/tuchedsf)

**autor:** Diego Santos Ferreira

**Date:** 1457270740092

## Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma.

```js
function sayName (name, callback){
  if (typeof name !=== 'string'){
    var err = new Error('nome invalido');
    return callback(err, null);
  }
    return callback(null, name);
}

sayName('Diego', function(err,name){
  if (err){
    console.log(err);
  }else{
    console.log(name);
  }
});
```

## Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.
```js
function calculo (valor1,valor2,callback){

  if (typeof valor1 !== 'number' || typeof valor2 !== 'number'){
    var err = new Error('Valores devem ser numericos');
    return callback(err, null);
  }

  var soma = valor1 + valor2;

    return callback(null, soma);
}

calculo(1,2, function(err,result){
  if (err){
    console.log(err);
  }else{
    console.log(result);
  }
});
```

## Criar uma função que calcula a média de dois valores e imprima essa média em outra função, como continuação da execução da mesma.
```js
function calculoMedia (valor1,valor2,callback){

  if (typeof valor1 !== 'number' || typeof valor2 !== 'number'){
    var err = new Error('Valores devem ser numericos');
    return callback(err, null);
  }

  var media = (valor1 + valor2) /2;

    return callback(null, media);
}

calculoMedia(2,2, function(err,result){
  if (err){
    console.log(err);
  }else{
    console.log(result);
  }
});
```

## Explicar a definição de continuação de uma função.

A continuação de uma função se da quando uma função é passada como parametro para outra função. Quando isso ocorre a função passada como paramtro é processada e retorna o fluxo para a função que realizou a chamada continuar o processamento dando continuidade a mesma.


text:
# NodeJS - Aula 04 - Exercícios

**user:** [Geriel](https://github.com/geriel)

**autor:** Geriel Castro

## 1. Criar uma função com **uma entrada** para nome e **imprimir** está entrada em uma outra função, como continuação da execução da mesma.

```js

    function getNome(name, callback) {
        if (typeof name === 'string') {
            callback(null, name);
        }else{
            var err = new Error('Infelizmente deu Erro, reveja seu codigo.');
            callback(err, null);
        }
    };

    getNome('João Ubaldo', function(err, name) {
        if (err) {
            console.log('Erro: ' + err);
        } else {
            console.log('Resultado = ' + name);
        }
    });

Resultado = João Ubaldo

```
## 2. Criar uma função que **calcula a soma** de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.
```js

	function calculaSoma( num1, num2, callback){
        if(typeof num1 == "number" && typeof num2 == "number"){
            var result = num1 + num2;
            callback(null, result);
        }else{
            var err = new Error("Por favor, passe um número válido!");
            callback(err, null);
        }
    }
    calculaSoma(12, 14, function(err, result){
        if(err){
            console.log("Erro: " + err);
        }else{
            console.log("Resultado = " + result);
        }
    });

Resultado = 26

```
## 3. Criar uma função que **calcula a média** de dois valores e imprima essa média em uma outra função, como continuação da execução da mesma.

```js

    function calculaMedia(num1, num2, callback){
        setTimeout(function(){
            if(typeof num1 == "number" && typeof num2 == "number"){
                var result = (num1 + num2)/2;
                callback(null, result);
            }else{
                var err = new Error("Por favor, passe um número válido");
                callback(err, null);
            }
        })
    }

    calculaMedia(140, 60, function(err, result){
        if(err){
            console.log("Erro: "+ err);
        }else{
            console.log("Resultado = " + result);
        }
    })

    Resultado = 100

```
## 4. Explicar a definição de **continuação de uma função**.

Podemos dizer que é uma função executada que recebe outra função como parametro retornando valores.


text:
# Node.js - Aula 04 - Exercício

**user:** [victorvoid](https://github.com/victorvoid)

**autor:** Victor Igor

## Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma.

```js
'use strict'

function printName(name, callback){
  if (typeof name === 'string')
    return callback(null, name);
  let err = new Error('Invalid type argument!!!');
    return callback(err, null);
}
printName('Victor Igor', (err, result)=>{
  if(err)
    console.log(err);
  else
    console.log(result);
});

```

## Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.

```js
'use strict'

function sum(n1, n2, callback){
  if (typeof n1 === 'number' && typeof n2 === 'number')
    return callback(null, (n1+n2));
  let err = new Error('Invalid type argument!!!');
  return callback(err, null);
}

sum(2,3,(err, result)=>{
  if(err)
    console.log(err);
  else
    console.log(result);
});
```

## Criar uma que calcular a média de dois valores e imprima essa média uma outra função, como continuação da execução da mesma.

```js
'use strict'

function avg(n1, n2, callback){
  if(typeof n1 === 'number' && typeof n2 === 'number')
    return callback(null, ((n1+n2)/2));
  let err = new Error('Invalid type argument!!!');
  callback(err, null);
}

avg(4,16,(err, result)=>{
  if(err)
    console.log(err);
  else
    console.log(result);
})

```

## Explicar a definição de continuação de uma função.

```
Ela se baseia em você criar uma função1 que vai ter um objetivo e
nela, vem uma função2 passada por parâmetro para que a funcao2 possa ser chamada ali dentro, assim podendo passar por parâmetro qualquer valor a funcao2, para que a partir dali a funcao2 __continue__.
```


text:
# Node.js - Aula 04 - Exercício
**user:** [hc3](https://github.com/hc3)
**autor:** Eliel das Virgens
**date:** 20/03/2016

##01.Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da mesma.
```
function criaNome(nome,callback){
    if (typeof nome === 'string') {
        return callback(null,nome);
    } else {
        erro = new Error('Nome inválido!');
        return callback(erro,null);
    }
}

criaNome('Eliel', function(err, result){
    if (err) console.log(err);
    else console.log(result);
});
```

##02.Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.
```
function soma(a, b, callback){
    if (typeof a === 'number' && typeof b === 'number'){
        var resultado = a + b;
        return callback(null,resultado);
    } else {
        var erro = new Error('deu Zika');
        return callback(erro, null);
    }
}

soma(20,50,function(err,result){
    if (err) console.log(err);
    else console.log(result);
});
```

##03.Criar uma que calcular a média de dois valores e imprima essa média em uma outra função, como continuação da execução da mesma.
```
function calcularMedia(a, b, callback){
    if (typeof a === 'number' && typeof b === 'number'){
        var resultado = (a + b) / 2;
        return callback(null,resultado);
    } else {
        var erro = new Error('Valores inválidos');
        return callback(erro, null);
    }
}

calcularMedia(20,30,function(err,result){
    if (err) console.log(err);
    else console.log(result);
});
```

##04.Explicar a definição de continuação de uma função.
-A continuação de uma função, também chamado de callback é o ato de passar uma função como parâmetro de outra, isso acontecer porque as funções javascript são high order functions, e com isso podem ser atribuidas a variáveis ou passadas como parâmetro de outras funções ou seja se juntarmos isso as closures temos os callbacks vão seguir um padrão com o erro no primeiro parâmetro e o resultado no segundo, e com os callbacks podemos criar aplicações não blocantes.


text:
# Node.js - Aula 04 - Exercício
**User:** [DouglasHennrich](https://github.com/DouglasHennrich)

**Autor:** Douglas Hennrich

**Date:** 1450670429154

## Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma.
```js
function sayMyName(name, callback){
  if(typeof name !== 'string'){
    var err = 'O primeiro parâmetro precisa ser uma \'String\'';
    return callback(err, null);
  }

  return callback(null, name);
}

sayMyName('Douglas Hennrich', function(err, result){
  if(err){
    console.log(err);
    return
  }

  console.log(result);
});
```

## Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.
```js
function AplusB(A, B, callback){
  if( typeof A !== 'number' || typeof B !== 'number'){
    var err = 'Os parâmetros precisam ser \'Number\'';
    return callback(err, null);
  }

  var result = A + B;
  return callback(null, result);
}

AplusB(2, 4, function(err, result){
  if(err){
    console.log(err);
    return;
  }

  console.log(result);
});
```

## Criar uma função que calcula a média de dois valores e imprima essa média em outra função, como continuação da execução da mesma.
```js
function media(A, B, callback){
  if( typeof A !== 'number' || typeof B !== 'number'){
    var err = 'Os parâmetros precisam ser \'Number\'';
    return callback(err, null);
  }

  var result = ( A + B )/ 2;
  return callback(null, result);
}

media(2, 6, function(err, result){
  if(err){
    console.log(err);
    return;
  }

  console.log(result);
});
```

## Explicar a definição de continuação de uma função.
É quando passamos uma `function A` como parâmetro para `function B` que em determinado momento, a `function B` vai retornar alguma coisa para a `function A`, dando continuidade no fluxo do código.


text:
# Node.js - Aula 04 - Exercício
**Autor:** Gabriel Kalani
**Data:** 1454213678365

## Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma.

```js
function name(nome, callback){
  if (typeof nome === 'string') {
    return callback(null, nome);
  } else {
    const err = new Error("Opa! Parece que você não digitou uma string.");

    return callback(err, null);
  }
}

name("Gabriel Kalani", (err, nome) => {
  if (err) {
    console.log(err);
  } else {
    console.log(nome);
  }
});
```

Saída que apareceu no Console.

```
Desktop\Gabriel\workshop-be-mean\nodejs\callbacks>node name.js
Gabriel Kalani
```

## Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.

```js
function sum (num1, num2, callback) {
  setTimeout(() => {
    if (typeof num1 == "number" && typeof num2 == "number") {
      const result = num1 + num2;
      callback(null, result);
    } else {
      const error = new Error("Ué?! Cadê o número?");
      return callback(error, null);
    }
  }, 30);
}

sum(4, 4, (err, result) => {
  if (err) {
    console.log(err);
  } else {
    console.log(result);
  }
});
```

Saída:

```
Desktop\Gabriel\workshop-be-mean\nodejs\callbacks>node numbers.js
8
```

## Criar uma que calcular a média de dois valores e imprima essa média uma outra função, como continuação da execução da mesma.

```js
function sum (num1, num2, callback) {
  setTimeout(() => {
    if (typeof num1 == "number" && typeof num2 == "number") {
      const result = (num1 + num2)/ 2;
      callback(null, result);
    } else {
      const error = new Error("Ué?! Cadê o número?");
      return callback(error, null);
    }
  }, 30);
}

sum(4, 4, (err, result) => {
  if (err) {
    console.log(err);
  } else {
    console.log(result);
  }
});
```

Saída do Console:

```
\Desktop\Gabriel\workshop-be-mean\nodejs\callbacks>node numbers.js
4
```

## Explicar a definição de continuação de uma função.
Uma `function` que recebe no parâmetro, outra `function` ou dizendo `callback`.
A função passada como parâmetro vai enviar algo para a outra função para continuar com a execução do código.


text:
# Node.js - Aula 04 - Exercício
**user:** [fauker](http://github.com/fauker)
**autor:** LUCAS DA SILVA MOREIRA

## Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma

```
function sayMyName(nome, callback) {
  if (typeof nome === 'string') {
    return callback(null, nome);
  } else {
    return callback(new Error('O parâmetro passado não é uma String!'), null);
  }
}

sayMyName('Lucas', function(erro, resposta) {
  if (erro) console.log(erro);
  else console.log(resposta);
});
```

## Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula

```
function sum(num1, num2, callback) {
  if (typeof num1 === 'number' && typeof num2 === 'number') {
    var result = num1 + num2;
    return callback(null, result);
  } else {
    return callback(new Erro('Somente números são suportados'), null);
  }
}

sum(1, 2, function(error, result) {
  if (error) console.log(error);
  else console.log(result);
});
```

## Criar uma função que calcula a média de dois valores e imprima essa média em outra função, como continuação da execução da mesma

```
function media(num1, num2, callback) {
  if (typeof num1 === 'number' && typeof num2 === 'number') {
    var result = num1 + num2 / 2;
    return callback(null, result);
  } else {
    return callback(new Erro('Somente números são suportados'), null);
  }
}

media(1, 2, function(error, result) {
  if (error) console.log(error);
  else console.log(result);
});
```

## Explicar a definição de continuação de uma função

Na programação funcional, **Continuation-passing style** é um estilo de
programação que o controle é passado explicitamente em forma de
continuação.

A função escrita com este padrão tem um argumento extra: uma função que
será a continuação.


text:
# Node.js - Aula 04 - Exercício
**User:** [daniofilho](https://github.com/daniofilho)

**Autor:** Dânio Filho

**Date:** 1452804479379

# 1. Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma.

~~~ js

function nome(name, cb){
   if(typeof name === 'string'){
      return cb(null, name);
   } else {
      var err = new Error('Por favor, insira uma string.');
      return cb(err, null);
   }
}

nome('Dânio', function(err, result){
   if(err){
      console.log(err);
   } else {
      console.log(result);
   }
});

~~~

# 2. Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.

~~~ js

function soma(v1, v2, cb){
   if(typeof v1 === 'number' && typeof v2 === 'number'){
      return cb(null, v1 + v2);
   } else {
      var err = new Error('Por favor, utilize números.');
      return cb(err, null);
   }
}

soma(2, 3, function(err, result){
   if(err){
      console.log(err);
   } else {
      console.log(result);
   }
});

~~~

# 3. Criar uma função que calcula a média de dois valores e imprima essa média numa outra função, como continuação da execução da mesma.

~~~ js
function media(v1, v2, cb){
   if(typeof v1 === 'number' && typeof v2 === 'number'){
      return cb(null, (v1 + v2)/2);
   } else {
      var err = new Error('Por favor, utilize números.');
      return cb(err, null);
   }
}

media(10, 3, function(err, result){
   if(err){
      console.log(err);
   } else {
      console.log(result);
   }
});
~~~

# 4. Explicar a definição de continuação de uma função.

Usar a técnica de continuação de uma função garante que você consiga executar as funções e processar sua saída de forma contínua, ou seja, em sequência, como se fossem síncronas, mesmo que as funções sejam assíncronas.


text:
# Node.js - Aula 04 - Exercício
**user:** [airton](https://github.com/airton)

**autor:** Airton Vancin Junior

**date:** 1456328185817

## Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma

```js

function sayName (name, callback){
    if (typeof name === 'string') {
        return callback(null, name);
    } else {
        var err = new Error("Error, não é string");
        return callback(err, null);
    }
}

sayName("Airton Vancin", function(err, name){
    if (err) {
        console.log(err);
    } else {
        console.log(name);
    }
});

```

## Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula

```js
function sum (num1, num2, callback) {
    if (typeof num1 == "number" && typeof num2 == "number") {
        var result = num1 + num2;
        callback(null, result);
    } else {
        var error = new Error("Passe um número, por favor!! ");
        return callback(error, null);
    }
}

sum(10, 10, function(err, result){
    if (err) {
        console.log(err);
    } else {
        console.log(result);
    }
});

```

## Criar uma função que calcula a média de dois valores e imprima essa média em outra função, como continuação da execução da mesma

```js
function sum (num1, num2, callback) {
    if (typeof num1 == "number" && typeof num2 == "number") {
        var result = (num1 + num2) / 2;
        callback(null, result);
    } else {
        var error = new Error("Passe um número, por favor!! ");
        return callback(error, null);
    }
}

sum(50, 100, function(err, result){
    if (err) {
        console.log(err);
    } else {
        console.log(result);
    }
});

```

## Explicar a definição de continuação de uma função

Para estendermos a execução de uma função basta que passamos para essa função um outra função com parâmentro.


text:
# NodeJS - Aula 04 - Exercícios
**User:** [ajoao1988](https://github.com/ajoao88)
**Autor:** João Paulo S de Araújo
**Date:** 1456710048954

## 1. Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da mesma.

```js
function imprimirNome(nome,callback){
    if (typeof nome === 'string') {
        return callback(null,nome);
    } else {
        erro = new Error('Nome inválido!');
        return callback(erro,null);
    }
}

imprimirNome('João Paulo', function(err, result){
    if (err) console.log(err);
    else console.log(result);
});
```

## 2. Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.

```js
function soma(val1, val2, callback){
    if (typeof val1 === 'number' && typeof val2 === 'number'){
        var resultado = val1 + val2;
        return callback(null,resultado);
    } else {
        var erro = new Error('Valores inválidos');
        return callback(erro, null);
    }
}

soma(12,13,function(err,result){
    if (err) console.log(err);
    else console.log(result);
});
```

## 3. Criar uma que calcular a média de dois valores e imprima essa média em uma outra função, como continuação da execução da mesma.

```js
function calcularMedia(val1, val2, callback){
    if (typeof val1 === 'number' && typeof val2 === 'number'){
        var resultado = (val1 + val2) / 2;
        return callback(null,resultado);
    } else {
        var erro = new Error('Valores inválidos');
        return callback(erro, null);
    }
}

calcularMedia(12,13,function(err,result){
    if (err) console.log(err);
    else console.log(result);
});
```

## 4. Explicar a definição de continuação de uma função.
Continuação de uma função é a técnica de chamar uma função dentro de outra e ter o retorno da função chamada em forma de callback para a função chamadora e assim fazer a continuação do código após a chamada.


text:
# Node.js - Aula 04 - Exercício
**user:** [augustoody](https://github.com/AugustoOdy)
**autor:** Augusto Ody
**date:** 1455192903275

## Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma
```js
'use strict';

function sayMyName(name, callback){
  if(typeof name == 'string'){
    callback(null, name);
  }else{
    var err = new Error('O name não é uma string')
    callback(err, null);
  }
}

sayMyName('Augusto', function(err, name){
  if(err){
    console.log(err);
  }else{
    console.log(name);
  }
});
```

Resposta:
```
Augusto
```

## Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula
```js
'use strict';

function sum(n1, n2, callback){
  if(typeof n1 == 'number' && typeof n2 == 'number'){
    callback(null, n1+n2);
  }else{
    var err = new Error('O primeiro ou o segundo valor não é/são números.')
    callback(err, null);
  }
}

sum(1, 2, function(err, result){
  if(err){
    console.log(err);
  }else{
    console.log(`Resultado: ${result}`);
  }
});
```

Resposta:
```
Resultado: 3
```

## Criar uma função que calcula a média de dois valores e imprima essa média em outra função, como continuação da execução da mesma
```js
'use strict';

function sum(n1, n2, callback){
    setTimeout(function() {
       if(typeof n1 == 'number' && typeof n2 == 'number'){
            callback(null, (n1+n2)/2);
        }else{
            var err = new Error('O primeiro ou o segundo valor não é/são números.')
            callback(err, null);
        }
    }, 30);
}

sum(1, 2, function(err, result){
  if(err){
    console.log(err);
  }else{
    console.log(`Resultado: ${result}`);
  }
});
```

Resposta:
```
Resultado: 1.5
```

## Explicar a definição de continuação de uma função

Denominado continuation-passing style, um dos argumentos da função é uma outra função que trata da continuação de sua execução. Como exemplificado nos exercícios acima.


text:
# Node.js - Aula 04 - Exercício

**user:** https://github.com/paulosilva92

**autor:** Paulo Roberto da Silva

**date:** Sat Mar 05 2016 00:51:06 GMT-0300 (BRT)

## Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma.

```js
function name(name) {
    if (typeof(name) !== 'string') {
        var err = new Error('Não é uma String');
        arguments[1](err, null)
    } else {
        arguments[1](null,name);
    }
}

name("paulo", function(err,name){
    if (err) {
        console.log(err);
    } else {
        console.log(name);
    }
});
```

## Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.

```js
function soma(num1, num2 , callback) {
    if (typeof(num1) === 'number' && typeof(num2) === 'number') {
        callback(null,num1+num2);
    } else {
        var err = new Error('Parametros inválidos');
        callback(err, null)
    }

};

soma(1,2, function(err,resultado){
    if (err) {
        console.log(err);
    } else {
        console.log(resultado);
    }
});

```

## Criar uma função que calcula a média de dois valores e imprima essa média uma outra função, como continuação da execução da mesma.

```js
function media(num1, num2 , callback) {
    if (typeof(num1) === 'number' && typeof(num2) === 'number') {
        var med = (num1+num2)/2
        callback(null,med);
    } else {
        var err = new Error('Parametros inválidos');
        callback(err, null)
    }

};

media(1,2, function(err,resultado){
    if (err) {
        console.log(err);
    } else {
        console.log(resultado);
    }
});
```

## Explicar a definição de continuação de uma função

Quando estendemos a funcionalidade de uma função para outra função, dizemos que a segunda função é continuação da primeira.


text:
# NodeJS - Aula 04 - Exercício
autor: Wellerson Roberto

# Exercícios de File System

## 1) Criar um arquivo

```
'use strict';
const fs = require('fs');

fs.writeFile('./novo-arquivo.txt', '', (err) => {
    if (err)
        console.log(err);
});
```

## 2) Ler um arquivo
```
'use strict';
const fs = require('fs');

fs.readFile('./novo-arquivo.txt', 'utf8', (err, result) => {
    if (err)
        console.log(err);
    else
        console.log(result);
});
```

## 3) Editar conteúdo desse arquivo

```
'use strict';
const fs = require('fs');

let writeFileStream = fs.createWriteStream('./novo-arquivo.txt');

writeFileStream.write('Escrevendo novas coisas!');
```

## 4) Deletar arquivo

```
'use strict';
const fs = require('fs');

fs.unlink('./novo-arquivo.txt', (err) => {
    if (err)
        console.log(err);
});
```

## 5)Renomear o arquivo

```
'use strict';
const fs = require('fs');

fs.rename('./novo-arquivo.txt', 'renomeado.txt', (err) => {
    if (err)
        console.log(err);
});
```

## DESAFIO: Criar um servidor web de arquivos estáticos: .css, .html, .js e etc...

```
'use strict';

const http = require('http')
    , fs = require('fs')
    , buildHTML = text => {
        let html = '<!doctype html>' +
            '<html>' +
            '<head><title>Servidor de Arquivos Estáticos</title><meta charset="utf-8"/></head>' +
            '<body>' + text + '</body>' +
            '</html>';

        return html;
    };

http.createServer((req, res) => {
    let url = req.url;

    if (url.indexOf('/request') > -1) {
        url = url.substring(9);
        fs.readFile('./' + url, 'utf8', (err, result) => {
            if (err) {
                res.writeHead(200, { 'Content-Type': 'text/html' });
                res.write(buildHTML('Erro' + err));
                res.write(err);
            }
            else{
                res.writeHead(200, { 'Content-Type': 'text/plain' });
                res.write(result);
            }

            res.end();
        });
    }
    else {
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.write(buildHTML('Servidor de Arquivos Estáticos -> Use a URL "request/NOME DO ARQUIVO" para requerer seu arquivo!'));
        res.end();
    }
}).listen(3000, () => {
    console.log('Estou pronto!')
})
```

# Exercícios de Callback

## 1) Criar uma função com uma entrada para nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma.

```
'use strict';

const name = (name, callback) => {
    callback(name);
};

name('Wellerson Roberto', name => console.log(name));
```

## 2) Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado na aula.

```
'use strict';

const soma = (valor1, valor2, callback) => {
    if (typeof valor1 == 'number' && typeof valor2 == 'number')
        callback(null, (valor1 + valor2));
    else {
        var err = new Error('Você deve passar dois números filho da puta!');
        callback(err, null);
    }
};

soma(7, 2, (err, value) => {
    if (err) {
        console.log(err);
    }
    else {
        console.log(value);
    }
});
```

## 3) Criar uma função para calcular a média de dois valores e imprima essa média em uma outra função, como continuação da execução da mesma.

```
'use strict';

const media = (valor1, valor2, callback) => {
    if (typeof valor1 == 'number' && typeof valor2 == 'number'){
        let media = (valor1 + valor2) / 2
        callback(null, media);
    }

    else {
        var err = new Error('Você deve passar dois números filho da puta!');
        callback(err, null);
    }
};

media(7, 2, (err, value) => {
    if (err) {
        console.log(err);
    }
    else {
        console.log(value);
    }
});
```

## 4) Explicar a definição de continuação de uma função.

É uma forma de se obter funções sendo executada após a outra, mesmo que essas funções sejam assíncronas. Isso é obtivo passando-se a função por parâmetro para uma função assíncrona. Assim que essa função assíncrona terminar de ser executava, ela chamará a função passada como parâmetro, obtendo a mesma continuidade que você obtem se executar isso de forma síncrona.


text:
# Node.js - Aula 04 - Parte 1 - Exercício

**User:** [angelorubin](https://github.com/angelorubin)

**Autor:** Angelo Rogério Rubin

**Date:** 1451048271400

## Criar uma função com uma entrada para o nome e imprimir esta entrada em uma outra função, como continuação da execução da mesma.

    'use strict';

    function firstName(firstName, callback) {
        if(typeof firstName === 'string') {
            return callback(firstName, null);
        }
        else {
            var error = new Error('Digite seu primeiro nome.');
            return callback(error, null);
        }
    }

    firstName(2, (error, firstName) => {
        if(error) {
            console.log(error);
        }
        else {
            console.log(firstName);
        }
    });

## Criar uma função que calcula a soma de dois valores e passe o resultado em uma outra função e imprima-o, de acordo com o padrão apresentado em aula.

    'use strict';

    function addTwoNumbers(num1, num2, callback){
        if( typeof num1 !== 'number' || typeof num2 !== 'number'){
            let error = new Error('São permitidos apenas números');
            return callback(error, null);
        }

        let result = num1 + num2;
        return callback(null, result);
    }

    addTwoNumbers(20, 1.78, (error, result) => {
        if(error){
            console.log(error);
            return;
        }
        console.log(result);
    });

## Criar uma função que calcula a média de dois valores e imprima essa média uma outra função. como continuação da execução da mesma.

    'use strict';

    function media(val1, val2, callback) {
        if( typeof val1 != 'number' || typeof val2 != 'number'){
            var error = new Error('São permitidos apenas números.');
            return callback(error, null);
        }
        var result = ( val1 + val2 )/ 2;
        return callback(null, result);
    }

    media(2, 1.45, (error, result) => {
        if(error){
            console.log(error);
            return;
        }
        console.log(result);
    });

## Explicar a definição de continuação de uma função.

Na programação funcional, continuation-passing style (CPS) é um estilo de programação em que o controle é passado explicitamente na forma de uma continuação.

Uma função escrita em continuation-passing style recebe um argumento extra: uma "continuação" explícita, ou seja, uma função de um argumento.

Os exemplos desenvolvidos acima demonstram claramente o uso de continuation-passing style.