Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.env

notes/
notes.*

Expand Down
85 changes: 85 additions & 0 deletions 5-database/assignments/db-final-project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Database Final Project: Blog database

Your blog currently has a server with one or more endpoints for serving data to the front-end. But right now, the data is just stored in a placeholder file. We want to set up a database to create and store actual data!

We will want a database (I suggest "BlogAPI" unless your blog has a name) with two collections, for "posts" and "users". You should already have placeholder data from earlier phases of this project, now is the time to upload that through Compass as your "posts". You don't need to create the users collection manually, it will be created automatically by our schema constructor.

## Set up your database

Install `mongoose` and `dotenv` in your project. Write a `dbConnect` function that creates the mongodb connection via mongoose, and call that function in your `app.js`.

Use dotenv! Remember the two kinds of things that must be stored in environment variables are:

- secrets (such as JWT key and SALT)
- anything related to the development environment, such as port numbers, and paths outside the package. The path to your database localhost will be one of these. Remember to include the name of your database at the end of the connection string

## Add user authentication

Even though you probably don't have a web client that includes the register and login actions, you will need those endpoints in order to create valid test users and generate tokens. We will worry about adding that functionality to the client in the next unit when we learn React!

User authentication in MongoDB is fairly straightforward, so this can be very close to what you did for [users in the recipe app](./db-assignment-3.md). You will want to put more thought into the users schema for this one, though. What will "users" on your blog have? Take a look at how you designed your blog's user profile page, what is there? A bio, a tagline, a profile photo? Do they have a handle or display name that is different from their username? Make sure everything is represented on the user schema.

Remember, not every field has to be required! In fact, only fields that a user _must_ fill out when they sign up for an account should be required.

> Note: For now, if you have photos you can serve them as static files, like we learned in the Server unit. Have your documents store the _static path_ of the image. We will learn how to handle things like user-uploaded images in a later unit.

## Create your Posts schema

Your posts need to include at least these required fields:

- body: String (this is the text of the post)
- userId: ObjectId (the user who made the post)
- createdAt: timestamp, we don't set these fields manually. See the [schemas lesson notes](../mongoose-schemas/models/Book.js) for an example

What else? Again, look at your site design to see what else is included. Do posts have titles? Images? (Any information related to the user, such as their display name, can be accessed through the userId and doesn't need to be included on this schema separately.) Take the time to think through which fields are required and which are optional.

Once your schema is created, go back to your endpoints and update them to use the schema.

Now is a good time to think about which of your endpoints need to be behind token validation. Often the GET endpoints will be public and the POST, PUT, and DELETE endpoints will require authentication. But this is your blog site and your decision!

## Test with your client!

If you have updated your endpoints without changing their functionality, you shouldn't have to make any changes to calls to your public endpoints, and your blog site should still work as it previously does.

For any endpoints you put behind token validation, you will need to add something to your `fetch` calls. They will need a second argument that looks like this:

```javascript
fetch(`http://localhost:4000/api/...`, {
headers: {
authorization: token goes here...,
},
})
```

You will just paste in a hard-coded token here for testing purposes, but once we implement real login behavior, the client will be able to get the user's token from the cookies.

#### CORS

Depending on your browser, you may get a "Cross-Origin Resource Sharing" error in your browser. We will talk more about CORS in a later unit, but in the meantime you can probably circumvent the error with these steps

- run `npm i cors` in your npm package
- in your app.js
- `import cors from "cors"` at the top
- `app.use(cors())` somewhere _before_ you apply your controllers

## Thinking forward

Right now your client doesn't do a whole lot, so you might only have a couple of GET endpoints. Soon the app will need to do a lot more! Can you "write ahead" some endpoints that will be ready for the next phase of this project? What are the actions we foresee? Creating, editing, and deleting posts are obvious ones. Can you think of anything else a user might be able to do in your blog app?

What about users? Can they change their bios, display names, or anything else? (Leave profile photos for a future unit, for now let's keep using hard-coded ones, or not include them at all.) Can users delete their whole profiles?

If you're adding CRUD endpoints for users, does it make sense for those to be in the same controller as the authorization-related endpoints, `register` and `login`? Think about separation of concerns. It's okay to add more controllers if it keeps things tidy!

(You may be tempted at this point to start thinking about likes and comments, but unless you are feeling very confident with this material I will discourage you from making the project overly complicated at this phase. Likes and comments will probably require more schemas, and more thought about foreign key relationships.)

I leave it to you how much of this you want to do now, but I will warn you that React is a big topic and the next projects will be big lifts! If you write these endpoints now, later you will be glad you did.

Again, it's not necessary to implement this functionality in your client yet. Just write the endpoints and test them with Postman. Don't forget to include tokens as needed, and don't forget that your tokens expire!

## Refreshers (optional)

Unless you really love writing event listeners, I don't think that actually implementing new functionality (such as creating posts) into your client at this point will be a good use of your time. We will soon be learning a whole new approach to writing web clients.

However, some of the actions we will be implementing soon will require new designs. Take some time to think about all these user actions, and whether they're already reflected in your blog's HTML. Where will users add new posts from? Is there a field at the top of the home page, or is it a separate page? What about login? Registering a new account? Editing or deleting a post?

Now would be a great time to build new pages or adjust your existing pages to prepare for the next phases of the project. Any structural HTML you write will be usable in React, but don't worry about making the forms actually submit or anything like that.
7 changes: 6 additions & 1 deletion 5-database/user-auth/app.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// this line reads the .env file and places it
import "dotenv/config"
import express from "express"
import { dbConnect } from "./db.js"
import tokenValidation from "./middlewares/tokenValidation.js"

const app = express()
const PORT = 4000
const PORT = process.env.SERVER_PORT

// we can access any environment variables on process.env
console.log(process.env.ANYTHING)

app.use(express.json())

Expand Down
4 changes: 2 additions & 2 deletions 5-database/user-auth/controllers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import jwt from "jsonwebtoken"

import User from "../model/User.js"

const SALT = 12
const JWT_KEY = "this is a secret"
const SALT = Number(process.env.SALT)
const JWT_KEY = process.env.JWT_KEY

// create new account
router.post("/register", async (req, res) => {
Expand Down
4 changes: 3 additions & 1 deletion 5-database/user-auth/db.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import mongoose from "mongoose"

const DB_URL = process.env.DB_URL

const dbConnect = async () => {
try {
await mongoose.connect("mongodb://localhost:27017/BooksAPI")
await mongoose.connect(DB_URL)
console.log(`[database]: connected to db`)
} catch (err) {
console.warn(`[database error]: ${err}`)
Expand Down
13 changes: 13 additions & 0 deletions 5-database/user-auth/example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# lines starting with # are comments in .env files

# each of these keys will be added as an environment variable
# with the given value

# your file should be named .env
# with no caps, or any other differences

SERVER_PORT=4000
DB_URL=mongodb://localhost:27017/BooksAPI
SALT=12
JWT_KEY = this is a secret
ANYTHING = works
27 changes: 25 additions & 2 deletions 5-database/user-auth/model/Book.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,52 @@ import { mongoose } from "../db.js"
/*
NOTE:
this is just a copy of the schema from the mongoose-schemas lesson
except we're adding more validators
*/

// custom validator functions must return a boolean
const loveValidator = (value) => {
if (value.includes("Love")) {
return true
}
return false
}

const Book = new mongoose.Schema(
{
title: {
type: String,
required: true,
maxlength: 150,
maxLength: [
// if the value is an array, the second element is the error message
150,
// error messages have access to {VALUE}
"Titles must be 150 charactes or less. {VALUE} is too long",
],
trim: true,
// a custom validator can be a function object
// validate: loveValidator,
// or an object with keys for the function and for the error message
validate: {
validator: loveValidator,
message: "This library is only for books about love",
},
},
author: {
type: String,
required: false,
default: "Author Unknown",
maxlength: 150,
},
// for arrays, just wrap the validator details in square brackets!
ratings: [{ type: String, maxlength: 50 }],
// an enum is a choice of limited options, like a drop-down menu
// genre: {
// type: String,
// required: false,
// enum: ["nonfiction", "history", "whatever"],
// },
},
// this will include createdAt and updatedAt timestamps by default
{ timestamps: true }
)

Expand Down
4 changes: 4 additions & 0 deletions 5-database/user-auth/model/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const User = new mongoose.Schema({
type: String,
required: true,
unique: true,
// match takes a regular expression string
// only strings that match the regex will be validated
match:
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/,
},
// we won't put other contraints here
// because we won't be storing the actual passwords
Expand Down
12 changes: 12 additions & 0 deletions 5-database/user-auth/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions 5-database/user-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"description": "",
"dependencies": {
"bcrypt": "^5.1.1",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.7.0"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This is the central repository for IBT Learning's Full Stack Software Engineerin
| 21 | Using the MongoDB driver | [MongoDB practice](./5-database/assignments/db-assignment-1.md) |
| 22 | Mongoose Schemas | [Create a CRUD app with Mongoose](./5-database/assignments/db-assignment-2.md) |
| 23 | User Authentication | [Add users to your recipe app](./5-database/assignments/db-assignment-3.md) |
| 23 | Database Final Project | [Database Final Project](./5-database/assignments/db-final-project.md) |

### Server

Expand Down
24 changes: 24 additions & 0 deletions code/react-folder/react-assignment/random-quotation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
8 changes: 8 additions & 0 deletions code/react-folder/react-assignment/random-quotation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# React + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'

export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
settings: { react: { version: '18.3' } },
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]
13 changes: 13 additions & 0 deletions code/react-folder/react-assignment/random-quotation/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Loading