Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/tdkent/birdiary-server


https://github.com/tdkent/birdiary-server

Last synced: 8 days ago
JSON representation

Awesome Lists containing this project

README

        

Birdiary


A web-based application to track bird sightings and manage a birdwatching life list.

## Description

This is a Node.js REST API built with TypeScript, NestJS and Prisma. The server handles requests to create and authenticate users and their birdwatching activities. The database stores user and bird sighting records, and general information about more than 800 North American species of bird. The API routes, validates, and processes a variety of requests and queries to support the Birdiary client application.

## Important Scripts

#### Project setup

```zsh
$ npm install
```

#### Compile and run the project

```zsh
# development
$ npm run start

# watch mode
$ npm run start:dev

# production mode
$ npm run start:prod
```

#### Run tests

```zsh
# unit tests
$ npm run test

# e2e tests
$ npm run test:e2e
```

## Environment Variables

`PORT=3000`

`DATABASE_URL=`

`SHADOW_DATABASE_URL=`

- Note: the "shadow" database is necessary part of the `prisma migrate dev` command. [More information](https://www.prisma.io/docs/orm/prisma-migrate/understanding-prisma-migrate/shadow-database)

## Routes

### Routing Information

#### API Endpoints

Use either the `npm run start` or `npm run start:dev` scripts to initiate a local endpoint at port 3000. Use the following as _base_url_:

```
http://localhost:3000
```

#### Headers

All requests that include a request object should include `Content-Type: application/json` in the header.

All protected routes require a JWT token in the `Authorization` header of the request:

```
Authorization: Bearer
```

The user's ID is extracted from the token to complete the request.

#### Errors

Validation errors will return an error object with code `400 Bad Request` and a custom error message. Other error codes, including `404 Not Found` and `500 Internal Server` may be returned depending on the nature of the error.

### Users

#### Create new user

```
POST base_url + '/users'

Request object:

{ email, password }
```

Validation

- `email`: Required. Must be a valid email.
- `password`: Required. Must be an 8-36 character string.

Behavior: A new row will be added to `User`. Related rows with null data (aside from the generated `user_id`) will be added to `Profile` and `Favorite`.

Response object

```
{ id }
```

#### Delete user

```
DELETE base_url + '/users'
```

Authorization

- Protected route. Requires token in `Authorization` header.

Behavior: Deletes row in `User` with matching `id`. Deletion cascades to related rows in `Profile`, `Favorite`, and `Sighting`.

Response object

```
{ id }
```

#### (Auth) Sign in user

```
POST base_url + '/users/auth/signin'

Request object:

{ email, password }
```

Validation

- `email`: Required. Must match a stored email.
- `password`: Required. Must match the password related to the stored email.

Response object

```
{ token }
```

#### Find user by id

```
GET base_url + '/users/profile'
```

Authorization

- Protected route. Requires token in `Authorization` header.

Response object

```
{
email,
created_at,
profile: {
user_id,
name,
location
},
fav_bird: {
bird: {
id,
comm_name
}
} || null
}
```

#### Update user

```
PATCH base_url + '/users/profile'

Request object:

{ name?, location? }
```

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `name`: Optional. Maximum 36 characters.
- `location`: Optional Maximum 60 character string.

Response object

```
{ id }
```

#### Create/update favorite bird

```
PUT base_url + '/users/profile/fav/:id
```

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `:id`: Integer parameter. Required.

Response object

```
{ bird_id }
```

### Sightings

#### Create new sighting

```
POST base_url + '/sightings'

Request object:

{
bird_id,
date,
desc,
location:
{
name,
lat,
lng
}
}
```

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `bird_id`: Required. Must be a valid bird id in the Bird table.
- `date`: Required. Valid UTC date string between 01-01-1950 and current date.

```
// To generate a standardized UTC date string:

const date = new Date(Date.UTC(YYYY, MM, DD));
```

- `desc`: Optional. Max 150 characters.
- `location`: Optional. Nested object containing the following required keys:
- `name`: Required. Max 150 characters. Generated by Google Places API.
- `lng`: Required. Float between -180 and 180. Generated by Google Geocode API.
- `lat`: Required. Float between -90 and 90. Generated by Google Geocode API.

Response object

```
{
id,
bird_id,
location: {
id
}
}
```

#### Find all user's sightings

```
GET base_url + '/sightings'
```

Query Options

- The route may optionally include a `groupby` query with one of three values: `date`, `location`, `bird`

```
GET base_url + '/sightings?groupby=date'
GET base_url + '/sightings?groupby=bird'
GET base_url + '/sightings?groupby=location'
```

Authorization

- Protected route. Requires token in `Authorization` header.

Response object

```
// FIND ALL:
[
{
id,
date,
bird: {
id,
comm_name
},
location: {
id,
name
}
}
]

// GROUP BY: DATE
[
{
_count: {
_all
},
date
}
]

// GROUP BY: LOCATION
[
{
_count: {
_all
},
location_id,
location_name
}
]

// GROUP BY: BIRD
[
{
_count: {
_all
},
bird_id,
bird_name
}
]
```

#### Find user's recent sightings

```
GET base_url + '/sightings/recent/:page'
```

Returns paginated results of the user's recent sightings.

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `:page`: Integer parameter between 0-3

Response object

```
[
{
id,
date,
bird: {
id,
comm_name
}
}
]
```

#### Find user's life list

```
GET base_url + '/sightings/lifelist'
```

Returns user's distinct bird sightings (filtered by `bird_id` and oldest `date`).

Authorization

- Protected route. Requires token in `Authorization` header.

Response object

```
[
{
id,
date,
bird: {
id,
comm_name
},
location: {
id,
name
}
}
]
```

#### Find user's sightings by single date

```
GET base_url + '/sightings/date/:date'
```

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `:date`: String in ISO8601 Date format (YYYY-MM-DD). Valid dates are between 1950-01-01 and current date.

Response object

```
[
{
id,
desc,
bird: {
id,
comm_name
},
location: {
id,
name
}
}
]
```

#### Find user's sightings by single bird

```
GET base_url + '/sightings/bird/:id'
```

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `:id`: Integer parameter. Must be between 1 and current size of `Bird` table (currently: `838`).

Response object

```
[
{
id,
date,
desc,
location: {
id,
name
}
}
]
```

#### Find user's sightings by single location

```
GET base_url + '/sightings/locations/:id/all'
```

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `:id`: Integer parameter.

Response object

```
[
{
id,
date,
desc,
bird: {
id,
comm_name
}
}
]
```

#### Group user's sightings by single location

```
GET base_url + '/sightings/locations/:id/group'
```

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `:id`: Integer parameter.

Response object

```
[
{
_count: {
_all
},
bird_id,
comm_name
}
]
```

#### Update a sighting

```
PATCH base_url + '/sightings/:id'

Request object:

{ bird_id, date, desc, location }
```

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `:id`: Integer parameter.
- `bird_id`: Optional.
- `desc`: Optional. Max 150 characters.
- `date`: Optional. String in ISO8601 Date format (YYYY-MM-DD). Valid dates are between 1950-01-01 and current date. To generate a date standardized to UTC, use the following pattern in the frontend application:

```
const date = new Date(Date.UTC(2024, 9, 9));
```

- `location`: Optional. Nested Location object.
- `name`: Required. Max 150 characters. Generated by Google Places API.
- `lng`: Required. Float between -180 and 180. Generated by Google Geocode API.
- `lat`: Required. Float between -90 and 90. Generated by Google Geocode API.

Response object

```
{ count }
```

#### Delete a sighting

```
DELETE base_url + '/sightings/:id'
```

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `id`: Integer parameter.

Response object

```
{ count }
```

### Location

#### Find single location

```
GET base_url + '/sightings/locations/:id'
```

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `:id`: Integer parameter.

Response object

```
{ id, name, lat, lng }
```

#### Update a location

```
PATCH base_url + '/sightings/locations/:id'
```

Upserts location to `Location`. Updates related sightings with new `location_id`.

Authorization

- Protected route. Requires token in `Authorization` header.

Validation

- `:id`: Integer parameter.
- `location`: Required. Nested Location object.
- `name`: Required. Max 150 characters. Generated by Google Places API.
- `lng`: Required. Float between -180 and 180. Generated by Google Geocode API.
- `lat`: Required. Float between -90 and 90. Generated by Google Geocode API.

Response object

```
{ count }
```

### Bird

#### Find all birds

```
GET base_url + '/bird'
```

Response object

```
[
{
id,
comm_name
}
]
```

#### Find a single bird

```
GET base_url + '/bird/:id'
```

Validation

- `id`: Integer parameter.

Response object

```
{
id,
comm_name,
sci_name,
rarity,
desc,
img_attr,
img_href
family: {
id,
name
}
}
```

## Dependencies

- TypeScript
- Node.js
- NestJS
- ESLint
- Prettier
- Jest
- Supertest

#### Packages

- @nestjs/platform-express
- NestJS uses Express under the hood as an HTTP platform
- @nestjs/jwt
- @nestjs/swagger
- bcrypt
- rxjs
- class-transformer
- class-validator
- jest-mock
- ts-jest
- ts-node

#### Required Types Packages

- @types/bcrypt
- @types/express
- @types/jest
- @types/node
- @types/supertest