This repository contains the source code to a film blog I started last year. It is a platform where I can explore my passion for movies and write for fun.
Though not my most "impressive" project, it's my favorite one to have worked on thus far. I have put several hundred commits into this project, all while experimenting with different technologies and learning along the way.
Source code has been moved here on October 25th, 2024. It was previously part of my personal website's respository.
Getting Started
- Gradle
- MySQL
- Redis
- Node
- Setup environment variables.
export ADMIN_PW=<your-admin-password>
export JWT_SECRET_KEY=<your-jwt-secret-key>
Set
ADMIN_PW
equal to a secure, hashed password. Generate one using openssl or any password manager, and then hash it using Spring Security'sBCryptPasswordEncoder
. To generate a JWT secret key, use openssl rand -base64 512.
- Create a MySQL database.
Create a new database called
film_blog
. Make sure that your MySQL master username and password are both set to root.
- Install dependencies and run projects.
Open three terminal instances. Follow the below code blocks to run each app.
cd backend/java
./gradlew bootRun
cd frontend/admin
npm i
npm start
cd frontend/main
npm i
npm start
Unit Tests
Auth Package
- AuthService.java
- willThrowLoginWhenWrongPassword
- canLogin
- willThrowValidateTokenWhenTokenIsMalformed
- willThrowValidateTokenWhenTokenIsUnauthorized
- canValidateToken
- AuthUtil.java
- willReturnFalseDuringIsAuthHeaderValidWhenHeaderIsNull
- willReturnFalseDuringIsAuthHeaderValidWhenHeaderIsBlank
- willReturnFalseDuringIsAuthHeaderValidWhenHeaderIsEmpty
- willReturnFalseDuringIsAuthHeaderValidWhenHeaderStartsWithWrongPrefix
- willReturnTrueDuringIsAuthHeaderValidWhenHeaderIsValid
- JwtService.java
- canGetSigningKey
- canGenerateToken
- willThrowValidateTokenWhenTokenIsNotJwt
- willThrowValidateTokenWhenTokenUsesWrongSigningKey
- willThrowValidateTokenWhenTokenIsExpired
- canValidateToken
Cache Package
- CacheDao.java
- canSet
- canGet
- canDelete
- CacheService.java
- willReturnNullDuringGetWhenKeyNotFound
- canGetValueUsingClazz
- canGetValueUsingTypeReference
- willReturnDuringSetWhenDataIsNull
- canSet
- canDelete
Post Package
- PostService.java
- canGetAllPostsWhenCacheHit
- canGetAllPosts
- willThrowUpdatePostWhenPostNotFound
- canUpdatePost
- willThrowDeletePostWhenNotFound
- canDeletePost
- PostUtil.java
- willThrowConstructPostWhenTextHasNoTitle
- willThrowConstructPostWhenTextHasNoContent
- canConstructPost
- willThrowGetImageWhenMultipartFileIsNull
- willThrowGetImageWhenMultipartFileIsEmpty
- willThrowGetImageWhenMultipartFileHasSizeZero
- willThrowGetImageWhenMultipartFileHasInvalidFileExtension
- willThrowGetImageWhenMultipartFileHasInvalidContentType
- canGetImage
Health Package
- HealthService.java
- canGetHealthWithDatabasesUp
- canGetHealthWithDatabasesDown
- canGetHealthWithMysqlUpAndRedisDown
- canGetHealthWithMysqlDownAndRedisUp
- HealthUtil.java
- canGetUptime
- canGetMysqlUpStatus
- canGetMysqlDownStatus
- canGetRedisUpStatus
- canGetRedisDownStatus
Util Package
- StringUtil.java
- willReturnFalseDuringIsStringValidWhenStringIsNull
- willReturnFalseDuringIsStringValidWhenStringIsBlank
- willReturnFalseDuringIsStringValidWhenStringIsEmpty
- willReturnTrueDuringIsStringValidWhenStringIsValid
- DateUtil.java
- canGetTotalHours
- canGetRemainingMinutes
- canGetRemainingSeconds
- canGetRemainingMillis
Deployed using DigitalOcean + Vercel.
MySQL on Ubuntu
-
Install MySQL: https://ubuntu.com/server/docs/install-and-configure-a-mysql-server
-
Login to MySQL and create a new user, database, and grant privileges.
sudo mysql
CREATE USER '<USERNAME>'@'%' IDENTIFIED BY '<PASSWORD>';
CREATE DATABASE film_blog;
GRANT ALL PRIVILEGES ON film_blog.* TO '<USERNAME>'@'%';
FLUSH PRIVILEGES;
- Initialize the database with tables.
USE film_blog;
# source all migration code from ./backend/java/src/main/resources/db/migration
Redis on Ubuntu
-
Install Redis: https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/
-
Generate a new Redis password.
openssl rand -base64 512
- Edit the Redis config change the password.
redis-cli
CONFIG SET requirepass <password>
Backend Deployment
-
Install Nginx: https://ubuntu.com/tutorials/install-and-configure-nginx#1-overview
-
Install Certbot and follow its instructions for Nginx: https://certbot.eff.org/
-
Configure Nginx.
sudo vi /etc/nginx/sites-enabled/<domain>
Paste the following:
server {
listen 80;
server_name <domain>;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name <domain>;
ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/<domain>/chain.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
- Ensure DNS settings are configured properly.
Github Actions CI/CD Secrets
-
Navigate to the GitHub repository, click Settings. Under Security, click Secrets and variables and Actions.
-
Set the following secrets:
Set
ADMIN_PW
to your secure, hashed password for logging into the admin platform.
Set
JWT_SECRET_KEY
to your JWT signing key.
Set
SPRING_DATASOURCE_PASSWORD
to the MySQL user password.
Set
SPRING_DATASOURCE_USERNAME
to the MySQL user username.
Set
SPRING_DATA_REDIS_PASSWORD
to the Redis server authentication password.
Set
SSH_HOST
to the IP address of the EC2 instance hosting the Spring Boot app.
Set
SSH_KEY
to the content in the keypair that authorizes SSH connections to the EC2 instance hosting the Spring Boot app.
Set
TEST_ADMIN_PW
to a secure, hashed password for logging into the admin platform for integration tests only.