A platform connecting restaurants with food influencers for promotional collaborations and marketing partnerships.
This web application facilitates connections between restaurants looking to promote their businesses and food influencers seeking partnerships. Restaurants can post gigs, while influencers can browse and apply to these opportunities. The platform supports profile management, gig postings, applications, and more.
This project uses a classic three-tier web architecture, containerized for easy local development and production deployment.
- Frontend: HTML, CSS, JavaScript (served as static files)
- Backend: PHP (PHP-FPM)
- Database: MariaDB (MySQL-compatible, local via Docker or AWS RDS in production)
- Web Server: Nginx (proxies PHP requests, serves static files)
- Containerization: Docker & Docker Compose
- External APIs: Instagram Graph API
- docker-compose.yml defines three main services:
db: MariaDB databaseapp: PHP application (custom image, runs PHP-FPM)web: Nginx web server (proxies to app, serves static files)
- Volumes are used for persistent database storage and sharing code between host and containers.
- Environment variables are used for secrets and configuration.
- Development:
- Run
docker-compose up -dto start all services locally. - Access the app at
http://localhost.
- Run
- Production:
- Deploy containers to a server (update DB connection to AWS RDS in
html/includes/db_connect.php). - Use environment variables for secrets.
- Deploy containers to a server (update DB connection to AWS RDS in
This project is configured for deployment to DigitalOcean. There are two main methods to deploy:
The repository includes a GitHub workflow that automatically deploys to DigitalOcean when changes are pushed to the main branch.
-
Push your changes to the main branch:
git add . git commit -m "Your commit message" git push origin main -
The workflow will:
- Build the Docker image
- Push it to Docker Hub
- SSH into the DigitalOcean droplet
- Pull the latest image
- Restart the application
- Run database seeding scripts if needed
-
Monitor the workflow execution in the GitHub Actions tab of your repository.
For manual deployment to a DigitalOcean droplet:
-
Build and push your Docker image:
docker build -t yourusername/ourawork:latest . docker push yourusername/ourawork:latest -
SSH into your DigitalOcean droplet:
ssh root@your-droplet-ip -
Create or update your docker-compose.yml file with proper environment variables.
-
Pull and start the containers:
cd /path/to/project docker compose down docker compose pull docker compose up -d -
Initialize the database (if needed):
bash db/seed_database.sh
-
Droplet Requirements:
- Ubuntu 22.04 or newer
- At least 2GB RAM
- Docker and Docker Compose installed
-
Database Options:
- Use the containerized MariaDB (included in docker-compose.yml)
- Use DigitalOcean Managed Database (update connection details in db_connect.php)
- Use AWS RDS (update connection details in db_connect.php)
-
Domain and HTTPS Setup:
- Point your domain to the droplet's IP address
- Install Certbot for Let's Encrypt SSL certificates:
sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d yourdomain.com
-
Environment Variables: Create a
.envfile on your droplet with necessary credentials:DB_HOST=db_host DB_USERNAME=db_user DB_PASSWORD=secure_password DB_NAME=marketplace INSTAGRAM_APP_ID=your_instagram_app_id INSTAGRAM_APP_SECRET=your_instagram_app_secret INSTAGRAM_REDIRECT_URI=https://yourdomain.com/instagram_auth.php SCRAPER_API_KEY=your_secure_key
-
View container logs:
docker compose logs -f -
Monitor container status:
docker compose ps -
Update the application:
git pull docker compose pull docker compose up -d -
Backup database:
docker exec -i ourawork-db-1 mysqldump -umktuser -pmkpass marketplace > backup.sql
.github/workflows/deploy.yml: Contains automated deployment workflowdocker-compose.yml: Defines the services to runDockerfile: Builds the PHP application containernginx.conf: Nginx configuration for the web server
- Container startup issues: Check logs with
docker compose logs - Database connection failures: Verify correct environment variables and network connectivity
- Permission issues: Ensure proper file permissions for volumes
For increased traffic, consider:
- Using DigitalOcean Managed Databases for database scalability
- Setting up load balancing with multiple droplets
- Implementing a caching layer with Redis
docker-compose.yml: Orchestrates servicesDockerfile: Builds the PHP application imagenginx.conf: Nginx server configurationhtml/includes/db_connect.php: Database connection settings
+-------------------+
| User Browser |
+--------+----------+
|
v
+--------+----------+
| Nginx | (web)
+--------+----------+
|
v
+--------+----------+
| PHP-FPM App | (app)
+--------+----------+
|
v
+--------+----------+
| MariaDB (DB) |
+-------------------+
- Nginx serves static files and proxies PHP requests to the app container.
- The PHP app handles business logic and database interaction.
- MariaDB stores all persistent data.
- Instagram API is used for influencer integrations.
- Backend: PHP
- Database: MySQL (MariaDB) / AWS RDS
- Frontend: HTML, CSS, JavaScript
- Server: Nginx
- Containerization: Docker & Docker Compose
- APIs: Instagram Graph API
- Docker and Docker Compose installed on your system
- Git for version control
- Meta Developer Account (for Instagram integration)
-
Clone the repository:
git clone <repository-url> cd food-influencer-marketplace -
Start the Docker containers:
docker-compose up -d -
Initialize the database:
docker-compose exec db mysql -umktuser -pmkpass marketplace < schema.sql -
Configure Instagram API (see Instagram Integration section below)
-
Access the application:
http://localhost
.
├── docker-compose.yml # Docker configuration
├── Dockerfile # PHP application Dockerfile
├── nginx.conf # Nginx web server configuration
├── schema.sql # Database schema
└── html/ # Application files
├── css/ # Stylesheets
│ └── site.css # Main stylesheet
├── includes/ # Reusable PHP components
│ ├── db_connect.php # Database connection and utilities
│ ├── header.php # Common header
│ ├── footer.php # Common footer
│ └── instagram_config.php # Instagram API configuration
├── uploads/ # User-uploaded files
│ └── profiles/ # Profile images
├── index.php # Entry point
├── dashboard.php # User dashboard
├── login.php # Authentication
├── register.php # User registration
├── profile.php # User profiles
├── post_gig.php # Create new gigs (restaurants)
├── browse_gigs.php # Browse available gigs (influencers)
├── gig_details.php # View gig details
├── apply_gig.php # Apply to gigs (influencers)
├── applications.php # Manage applications (restaurants)
├── instagram_auth.php # Instagram authentication
├── instagram_import.php # Import Instagram posts
├── instagram_posts.php # Manage Instagram posts
├── instagram_webhook.php # Instagram webhook endpoint
└── php.ini # PHP configuration
The application can use either a local Docker-based MariaDB instance or AWS RDS:
- Database: MariaDB
- Host:
db(Docker service name) - Username:
mktuser - Password:
mkpass - Database name:
marketplace
- Database: MariaDB/MySQL on AWS RDS
- Host:
ourawork.ckb40aqwiavq.us-east-1.rds.amazonaws.com(or your RDS endpoint) - Username:
admin(or your RDS username) - Password: Your RDS password
- Database name:
marketplace
The database connection is configured in html/includes/db_connect.php. To switch between environments, update the connection parameters in this file.
The application uses the following database tables:
id- Primary keyname- User's full nameemail- User's email (unique)password- Hashed passwordrole- Either 'restaurant', 'influencer', or 'admin'bio- User's bio/descriptionprofile_image- Path to profile photosocial_links- JSON containing social media linksrestaurant_details- JSON with restaurant-specific info (cuisine, address, etc.)influencer_details- JSON with influencer-specific info (audience size, rates, etc.)created_at- Account creation timestamp
id- Primary keyrestaurant_id- Foreign key to users tabletitle- Gig titledescription- Detailed descriptionlocation- Location informationbudget- Budget informationcreated_at- Creation timestamp
id- Primary keygig_id- Foreign key to gigs tableinfluencer_id- Foreign key to users tablemessage- Application messagestatus- Status (pending, accepted, rejected)applied_at- Application timestamp
id- Primary keyuser_id- Foreign key to users tableaccount_name- Friendly name for the Instagram accountinstagram_user_id- Instagram user IDinstagram_username- Instagram usernameaccess_token- Instagram access tokentoken_expires_at- Token expiration timestampcreated_at- Creation timestamp
id- Primary keyinfluencer_id- Foreign key to users tabletoken_id- Foreign key to instagram_tokens tableinstagram_media_id- Instagram media IDpost_url- URL to the Instagram postcaption- Post captionthumbnail_url- URL to the post thumbnailmedia_url- URL to the media filemedia_type- Type of media (image, video, carousel)instagram_created_at- Post creation timestamp on Instagramcreated_at- Import timestamp
- User registration and login
- Role-based access control (restaurant vs. influencer)
- Session management
- Enhanced user profiles with role-specific fields
- Profile image upload
- Social media integration
- Editable details
- Post gigs with details
- Review and respond to applications
- Manage multiple gigs
- Browse available gigs
- Apply to gigs with personalized messages
- Track application status
- Connect multiple Instagram accounts
- Import and display Instagram content
The platform includes comprehensive Instagram integration for influencers, allowing them to:
- Connect multiple Instagram business/creator accounts
- Import posts directly from Instagram
- Display posts on their profile
- Automatically refresh authentication tokens
- Receive real-time updates via webhooks
-
Create a Meta Developer account at developers.facebook.com
-
Create a new app with the Instagram Basic Display product
-
Configure your app:
- Add the Instagram Basic Display product
- Configure Valid OAuth Redirect URIs with your domain +
/instagram_auth.php - Add your domain to App Domains
- Note your App ID and App Secret
-
Update
html/includes/instagram_config.php:define('INSTAGRAM_APP_ID', 'your-app-id'); define('INSTAGRAM_APP_SECRET', 'your-app-secret'); define('INSTAGRAM_REDIRECT_URI', 'https://your-domain.com/instagram_auth.php');
-
Configure the Instagram webhook (optional for real-time updates):
- Set up a webhook in the Meta Developer Dashboard
- Point it to:
https://your-domain.com/api/instagram_webhook.php - Use the verify token defined in
html/instagram_webhook.php:food_influencer_webhook_token - Subscribe to fields:
media
-
For production, you can deploy the webhook using Cloudflare Workers (see configuration in your Cloudflare account)
The latest update adds support for managing multiple Instagram accounts per user:
- Connect Multiple Accounts: Users can connect and maintain multiple Instagram professional accounts with unique labels
- Account Management: Easily switch between accounts when importing and managing posts
- Post Organization: Posts are linked to specific Instagram accounts for better organization
- Automatic Database Updates: The system automatically upgrades existing database tables when connecting the first account
- User initiates authentication by clicking "Connect Instagram Account"
- User is redirected to Instagram authentication page
- After authorization, user is redirected back with a code
- The code is exchanged for a short-lived token
- Short-lived token is exchanged for a long-lived token (60 days)
- Token is stored in the database and can be refreshed
- Posts can be imported using the stored token
-
Create a new PHP file in the
htmldirectory -
Include the required files at the top:
<?php require_once 'includes/db_connect.php'; // Add page-specific logic here // Include header (after potential redirects) require_once 'includes/header.php'; ?> <!-- HTML content here --> <?php require_once 'includes/footer.php'; ?>
- Process form data before including the header
- Validate all user inputs
- Use prepared statements for database operations
- Handle errors gracefully
<?php
require_once 'includes/db_connect.php';
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 1. Validate data
// 2. Process form
// 3. Redirect or set message
}
require_once 'includes/header.php';
?>
<!-- Display success/error messages -->
<?php if ($success): ?>
<div class="alert alert-success"><?= htmlspecialchars($success) ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<!-- Form here -->Always use prepared statements:
// Query with parameters
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param('i', $user_id);
$stmt->execute();
$result = $stmt->get_result();
// Insert/update with parameters
$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?, ?)");
$stmt->bind_param('ss', $value1, $value2);
$stmt->execute();- Always validate file type and size
- Use unique filenames to prevent overwriting
- Store uploaded files outside web root when possible
- Set appropriate permissions
// Sample file upload code
if (!empty($_FILES['file']['name'])) {
$target_dir = "uploads/myfiles/";
$file_extension = pathinfo($_FILES["file"]["name"], PATHINFO_EXTENSION);
$new_filename = "file_" . time() . "." . $file_extension;
$target_file = $target_dir . $new_filename;
// Validate file
$allowed_types = ['jpg', 'jpeg', 'png', 'pdf'];
$max_size = 5 * 1024 * 1024; // 5MB
if ($_FILES["file"]["size"] > $max_size) {
$error = "File is too large";
} elseif (!in_array(strtolower($file_extension), $allowed_types)) {
$error = "File type not allowed";
} elseif (move_uploaded_file($_FILES["file"]["tmp_name"], $target_file)) {
// Success - file uploaded
$file_path = $target_file;
}
}- SQL Injection: Always use prepared statements for database queries
- XSS (Cross-Site Scripting): Always use
htmlspecialchars()when outputting user-input data - CSRF (Cross-Site Request Forgery): Implement CSRF tokens for forms
- Password Security: Passwords are hashed using PASSWORD_BCRYPT
- File Uploads: Validate file types and sizes to prevent malicious uploads
- Authentication: Sessions are used for maintaining user state
- API Security: Instagram tokens are stored securely and refreshed regularly
Key PHP settings are in html/php.ini:
- upload_max_filesize = 10M
- post_max_size = 20M
- memory_limit = 128M
Key Nginx settings in nginx.conf:
- client_max_body_size = 20M
- Various MIME type settings
If experiencing "413 Request Entity Too Large" errors:
- Check if your file exceeds the 10MB limit
- Verify Nginx configuration has proper client_max_body_size value
- Ensure PHP settings for upload_max_filesize and post_max_size are correct
- Check phpinfo.php to verify current settings
If experiencing database connection problems:
- Verify database credentials in db_connect.php
- Check if the database container is running (for local development)
- Ensure the schema has been imported
- If you get an error about the 'role' column not accepting 'admin', see the Seeding section above for how to alter the table.
- Make sure your server's IP address is allowed in the RDS security group
- Verify that the RDS instance is publicly accessible
- Check that the required database exists on RDS:
CREATE DATABASE marketplace;
- Import the schema and seed data:
mariadb -h your-rds-endpoint.amazonaws.com -u admin -p marketplace < db/schema.sql mariadb -h your-rds-endpoint.amazonaws.com -u admin -p marketplace < db/enhanced_seed.sql mariadb -h your-rds-endpoint.amazonaws.com -u admin -p marketplace < db/admin_setup.sql
If experiencing Instagram connection problems:
- "Session Invalid" Error: Clear cookies, try incognito mode, or reconnect the account
- "Invalid Redirect URI" Error: Ensure your redirect URI in the code exactly matches what's in the Meta Developer Dashboard
- Database Column Errors: The system will automatically update the database schema, but you can manually run the table creation from
instagram_config.php - Token Expiration: Use the "Refresh Token" button if a token has expired
- Webhook Testing: Use the Cloudflare worker URL for reliable webhook testing
- Follow the coding style of the project
- Comment your code appropriately
- Test thoroughly before submitting changes
- Document any new features or changes
[Your license information here]
To seed the database with initial data (including the admin user), use the provided script:
bash db/seed_database.shThis will:
- Apply the schema
- Seed users, gigs, and other data
- Set up the admin user
- Email:
[email protected] - Password:
password123
ERROR 1265 (01000): Data truncated for column 'role' at row 1
This means the role column in your users table does not yet allow the value 'admin'. To fix this, run:
ALTER TABLE users MODIFY role ENUM('restaurant', 'influencer', 'admin') NOT NULL;Then re-run the seed script or the admin setup SQL.
This project includes comprehensive automated tests to ensure code quality and functionality. The test suite covers unit tests, integration tests, and feature tests using the existing Docker MariaDB container.
tests/
├── bootstrap.php # Test environment setup
├── Unit/ # Unit tests for individual functions
│ ├── DatabaseConnectionTest.php
│ └── SecurityTest.php
├── Integration/ # Integration tests for database operations
│ ├── UserManagementTest.php
│ └── GigManagementTest.php
├── Feature/ # End-to-end feature tests
│ └── AuthenticationTest.php
└── run_tests.sh # Test runner script
-
Docker containers must be running:
docker-compose up -d
-
Install Composer (if not already installed):
curl -sS https://getcomposer.org/installer | php sudo mv composer.phar /usr/local/bin/composer -
Install test dependencies:
composer install --dev
The easiest way to get started with testing:
# Validate test environment (sets up test database automatically)
./tests/run_tests.sh validate
# Run all tests
./tests/run_tests.sh all# Install dependencies and set up environment
./tests/run_tests.sh install
./tests/run_tests.sh setup
# Validate test database connection and setup
./tests/run_tests.sh validate
# Run all tests
./tests/run_tests.sh all
# Run specific test suites
./tests/run_tests.sh unit # Unit tests only
./tests/run_tests.sh integration # Integration tests only
./tests/run_tests.sh feature # Feature tests only
# Run tests with coverage report (requires Xdebug)
./tests/run_tests.sh coverage
# Run specific test file
./tests/run_tests.sh file tests/Unit/SecurityTest.php
# Clean up test artifacts
./tests/run_tests.sh cleanup# Set environment variables for Docker database
export DB_HOST=127.0.0.1
export DB_PORT=3306
export DB_USERNAME=test_user
export DB_PASSWORD=test_pass
export DB_NAME=marketplace_test
# Run all tests
./vendor/bin/phpunit
# Run specific test suite
./vendor/bin/phpunit --testsuite=Unit
./vendor/bin/phpunit --testsuite=Integration
./vendor/bin/phpunit --testsuite=Feature
# Run specific test file
./vendor/bin/phpunit tests/Unit/SecurityTest.php- DatabaseConnectionTest: Tests database schema and table structure
- SecurityTest: Tests CSRF protection, input sanitization, and password hashing
- UserManagementTest: Tests user creation, authentication, and profile management
- GigManagementTest: Tests gig creation, applications, and status management
- AuthenticationTest: Tests complete user registration and login workflows
The tests use your existing Docker MariaDB container with a separate test database:
- Database:
marketplace_test(automatically created) - User:
test_user/test_pass(automatically created) - Host:
127.0.0.1:3306(Docker container port mapping)
The test database is automatically:
- Created when you run
./tests/run_tests.sh validate - Reset before each test run to ensure clean state
- Isolated from your development data
Current test status: ✅ All 47 tests passing
Authentication (Tests\Feature\Authentication)
✔ User registration workflow
✔ User login workflow
✔ Login with invalid credentials
✔ Registration validation
✔ Duplicate email registration
✔ CSRF protection
✔ Session management
✔ Role based access
Database Connection (Tests\Unit\DatabaseConnection)
✔ Database connection
✔ Users table exists
✔ Gigs table exists
✔ Applications table exists
✔ Instagram tokens table exists
✔ Instagram posts table exists
✔ Users table structure
✔ User role enum
Gig Management (Tests\Integration\GigManagement)
✔ Create gig
✔ Create gig with invalid restaurant
✔ Get gigs by restaurant
✔ Update gig
✔ Delete gig
✔ Apply to gig
✔ Duplicate application
✔ Get applications for gig
✔ Update application status
✔ Get applications by influencer
✔ Application status enum
Security (Tests\Unit\Security)
✔ Generate CSRF token
✔ Verify CSRF token valid
✔ Verify CSRF token invalid
✔ Verify CSRF token empty
✔ Sanitize input basic
✔ Sanitize input with HTML
✔ Sanitize input with whitespace
✔ Sanitize input with quotes
✔ Password hashing
✔ Email validation
✔ Session security
User Management (Tests\Integration\UserManagement)
✔ Create user
✔ Create user with duplicate email
✔ User login
✔ User login with wrong password
✔ User roles
✔ Update user profile
✔ Delete user
✔ Get users by role
✔ User created at timestamp
OK (47 tests, 150 assertions)
The test suite is designed to work with CI/CD pipelines. Test results are generated in JUnit XML format for integration with CI systems:
# Results are saved to:
tests/results/junit.xml # JUnit XML format
tests/results/teamcity.txt # TeamCity format
coverage/ # HTML coverage report (if Xdebug available)# Start Docker containers
docker-compose up -d
# Verify containers are running
docker ps# Validate and setup test database
./tests/run_tests.sh validate
# Manual database setup (if needed)
docker exec -it ourawork-db-1 mysql -uroot -pexample_root_pw -e "
CREATE DATABASE IF NOT EXISTS marketplace_test;
CREATE USER IF NOT EXISTS 'test_user'@'%' IDENTIFIED BY 'test_pass';
GRANT ALL PRIVILEGES ON marketplace_test.* TO 'test_user'@'%';
FLUSH PRIVILEGES;
"If port 3306 is already in use, you can modify the port mapping in docker-compose.yml:
db:
ports:
- "3307:3306" # Use different host portWhen adding new functionality, follow these guidelines:
- Unit Tests: Test individual functions and methods in isolation
- Integration Tests: Test database operations and component interactions
- Feature Tests: Test complete user workflows and business logic
Example test structure:
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class MyNewTest extends TestCase
{
protected function setUp(): void
{
// Set up test environment
}
public function testSomeFunctionality()
{
// Arrange
$input = 'test data';
// Act
$result = someFunction($input);
// Assert
$this->assertEquals('expected', $result);
}
}Current test coverage includes:
- ✅ Authentication and authorization
- ✅ Database operations and schema validation
- ✅ Security functions (CSRF, sanitization, password hashing)
- ✅ Core business logic (users, gigs, applications)
- ✅ Input validation and error handling
To generate coverage reports (requires Xdebug):
./tests/run_tests.sh coverage
open coverage/index.html # View coverage report