Grapevine is a full-stack web application for managing audio files with user authentication. The system consists of a Node.js backend service and a React frontend application.
grape.mov
- Copy and paste the provided ENV variables provided into the root directory's
.env
file - Build and start the containers:
docker-compose up --build
- The application will be available at:
- Frontend: http://localhost:3001 (Nginx server)
- Backend API: http://localhost:3000 (Node.js server)
-
Framework: Koa.js with TypeScript
-
Database: PostgreSQL with Sequelize ORM
-
Storage: AWS S3 for audio file storage
-
Authentication: JWT-based with HTTP-only cookies
-
Framework: React with TypeScript
-
Build Tool: Vite
-
Styling: Tailwind CSS
-
Routing: React Router
- Containerized using Docker
- Nginx serving the frontend static files
- Environment configuration via
.env
files
Column | Type | Constraints |
---|---|---|
id | INTEGER | PRIMARY KEY |
username | VARCHAR | NOT NULL, UNIQUE |
password | VARCHAR | NOT NULL |
passwordVersion | INTEGER | DEFAULT 0 |
admin | BOOLEAN | DEFAULT false |
createdAt | TIMESTAMP | NOT NULL |
updatedAt | TIMESTAMP | NOT NULL |
Column | Type | Constraints |
---|---|---|
id | INTEGER | PRIMARY KEY |
title | VARCHAR | NOT NULL |
description | TEXT | NOT NULL |
userId | INTEGER | REFERENCES Users(id) |
fileUrl | VARCHAR | NOT NULL |
metadata | JSONB | |
createdAt | TIMESTAMP | NOT NULL |
updatedAt | TIMESTAMP | NOT NULL |
If an error occured, an error
key will be present in the response body.
All authenticated endpoints require a valid JWT token in the auth-token
cookie. This cookie is automatically set after successful login and removed on logout. If admin updates user's password via the PUT /users/:id
endpoint, existing JWT tokens for the user will become invalid and he/she will have to re-login to retrieve a new JWT token.
Login with username and password.
Request Body:
{
"username": "string",
"password": "string"
}
Response:
{
"userId": "number",
"admin": "boolean",
"username": "string"
}
Logout the current user.
Verifies if the user and auth-token
(in cookies) is still valid. A 401
error will be returned if verification fails.
Response:
{
"userId": "number",
"admin": "boolean",
"username": "string"
}
List audio files with pagination and filtering.
Query Parameters:
Parameter | Type | Default | Description |
---|---|---|---|
page | number | 1 | Page number for pagination |
limit | number | 10 | Number of items per page |
search | string | Search term for filtering | |
tags | string | Comma-separated list of tags |
Response:
{
"audioFiles": [
{
"id": "number",
"title": "string",
"description": "string",
"fileUrl": "string",
"userId": "number",
"metadata": {
"key": "string",
"tags": ["string"]
},
"createdAt": "string",
"updatedAt": "string"
}
],
"pagination": {
"currentPage": "number",
"totalPages": "number",
"totalItems": "number",
"itemsPerPage": "number"
}
}
Get a pre-signed URL for uploading an audio file.
Request Body:
{
"fileName": "string",
"fileType": "string"
}
Response:
{
"presignedUrl": "string",
"key": "string"
}
Get a pre-signed URL for downloading/playing an audio file.
Response:
{
"presignedUrl": "string"
}
Create a new audio file entry.
Request Body:
{
"title": "string",
"description": "string",
"key": "string",
"tags": ["string"]
}
Update audio file metadata.
Request Body:
{
"title": "string",
"description": "string",
"tags": ["string"]
}
Delete an audio file.
List users with pagination.
Query Parameters:
Parameter | Type | Default | Description |
---|---|---|---|
page | number | 1 | Page number for pagination |
limit | number | 9 | Number of items per page |
Response:
{
"users": [
{
"id": "number",
"username": "string",
"admin": "boolean",
"createdAt": "string",
"updatedAt": "string"
}
],
"pagination": {
"currentPage": "number",
"totalPages": "number",
"totalItems": "number",
"itemsPerPage": "number"
}
}
Create a new user.
Request Body:
{
"username": "string",
"password": "string"
}
Update user details. User with existing session will get logged out when password is changed.
Request Body:
{
"username": "string",
"password": "string"
}
Delete a user and associated files.
- JWT Authentication stored in HTTP-only cookies
- Log user out if admin changed user's password during a session
- Password hashing using bcrypt
- Admin-only routes protection
- User-specific file access control: users can only access their own files
- Input validation using Joi
- Secure file uploads using S3 presigned URLs
Audio files are stored in AWS S3 with the following structure:
uploads/
├── {userId}/
│ ├── {timestamp}-{filename}
│ └── ...