Skip to content

Commit 76771a6

Browse files
committed
added docker option, logger and tests
1 parent 302ac87 commit 76771a6

20 files changed

+2797
-773
lines changed

.dockerignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.directory
2+
.eslintrc.json
3+
.prettierrc
4+
.gitignore
5+
.vscode
6+
storage
7+
data

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.vscode/
22
node_modules/
3-
store/
3+
storage/
4+
data/
45
dist/
56

67
.directory

Dockerfile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
FROM node:lts-alpine AS base
2+
3+
RUN apk add --no-cache openssl ffmpeg
4+
5+
ENV DOCKERIZE_VERSION v0.6.1
6+
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
7+
&& tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
8+
&& rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz
9+
10+
RUN mkdir -p ~/zhomemedia
11+
12+
WORKDIR ~/zhomemedia
13+
14+
COPY package.json .
15+
COPY yarn.lock .
16+
17+
FROM base AS dependencies
18+
19+
RUN yarn
20+
21+
FROM dependencies AS runtime
22+
23+
COPY . .

docker-compose.override.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
version: '3.9'
2+
3+
services:
4+
mongo:
5+
command: mongod --quiet --logpath /var/log/mongodb/mongodb.log
6+
ports:
7+
- 27017:27017
8+
api:
9+
environment:
10+
DEVELOPMENT: true
11+
volumes:
12+
- ./:/~/zhomemedia
13+
api-tests:
14+
build: .
15+
image: zhomemedia
16+
environment:
17+
DEVELOPMENT: true
18+
MONGO_HOST: mongo
19+
REDIS_SERVER: redis
20+
command: dockerize
21+
-wait tcp://mongo:27017 -wait tcp://redis:6379 -timeout 10s
22+
sh -c "yarn test"
23+
depends_on:
24+
- mongo
25+
- redis

docker-compose.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
version: '3.9'
2+
3+
services:
4+
mongo:
5+
image: mongo
6+
restart: always
7+
environment:
8+
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
9+
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
10+
MONGO_INITDB_DATABASE: zHomeMedia
11+
volumes:
12+
- ${MONGO_DATA_DIR}:/data/db
13+
- ./initdb:/docker-entrypoint-initdb.d
14+
redis:
15+
image: "redis:alpine"
16+
api:
17+
build: .
18+
image: zhomemedia
19+
environment:
20+
DEVELOPMENT: false
21+
MONGO_HOST: mongo
22+
REDIS_SERVER: redis
23+
STORAGE: /storage
24+
volumes:
25+
- ${STORAGE}:/storage
26+
command: yarn start
27+
ports:
28+
- ${PORT}:${PORT}
29+
depends_on:
30+
- mongo
31+
- redis

initdb/initialize.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// @ts-nocheck
2+
db.createCollection('users');
3+
db.users.insertOne({
4+
username: 'admin',
5+
6+
password: '$2b$10$D5xuub32dNxuBeUj3xRbquCjWcpfGYCFYSXGxfkJSnWHoxhkaXdYy',
7+
profile: '1',
8+
});

jest.config.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type {InitialOptionsTsJest} from 'ts-jest';
2+
3+
const config: InitialOptionsTsJest = {
4+
preset: 'ts-jest/presets/default-esm',
5+
testEnvironment: 'node',
6+
globals: {
7+
'ts-jest': {
8+
useESM: true,
9+
},
10+
},
11+
moduleNameMapper: {
12+
'^(\\.{1,2}/.*)\\.js$': '$1',
13+
},
14+
};
15+
16+
export default config;

package.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,50 @@
44
"private": true,
55
"type": "module",
66
"scripts": {
7-
"build": "npx tsc",
7+
"test": "jest",
8+
"build": "npx tsc -p tsconfig.build.json",
89
"start": "nodemon --config nodemon.json src/server.ts"
910
},
1011
"dependencies": {
1112
"bcrypt": "^5.0.1",
1213
"connect-redis": "^6.1.3",
1314
"cookie-parser": "^1.4.6",
1415
"cors": "^2.8.5",
16+
"date-fns": "^2.29.1",
1517
"dotenv": "^16.0.1",
1618
"express": "^4.18.1",
1719
"express-fileupload": "^1.4.0",
1820
"express-session": "^1.17.3",
1921
"mongodb": "4.7",
22+
"pino": "^8.3.1",
2023
"redis": "^4.1.1"
2124
},
2225
"devDependencies": {
2326
"@types/bcrypt": "^5.0.0",
2427
"@types/connect-redis": "^0.0.18",
2528
"@types/cookie-parser": "^1.4.3",
2629
"@types/cors": "^2.8.12",
30+
"@types/date-fns": "^2.6.0",
2731
"@types/dotenv": "^8.2.0",
2832
"@types/express": "^4.17.13",
2933
"@types/express-fileupload": "^1.2.2",
3034
"@types/express-session": "^1.17.4",
35+
"@types/jest": "^28.1.6",
3136
"@types/mongodb": "^4.0.7",
3237
"@types/node": "^18.0.1",
38+
"@types/pino": "^7.0.5",
39+
"@types/pino-pretty": "^4.7.5",
40+
"@types/supertest": "^2.0.12",
3341
"@typescript-eslint/eslint-plugin": "^5.30.6",
3442
"@typescript-eslint/parser": "^5.30.6",
3543
"eslint": "^8.20.0",
3644
"eslint-config-google": "^0.14.0",
3745
"eslint-plugin-jsdoc": "^39.3.3",
46+
"jest": "^28.1.3",
3847
"nodemon": "^2.0.16",
48+
"pino-pretty": "^8.1.0",
49+
"supertest": "^6.2.4",
50+
"ts-jest": "^28.0.7",
3951
"ts-node": "^10.8.1",
4052
"typescript": "^4.7.4"
4153
}

src/app.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import video from './endpoints/video.js';
1111
import videos from './endpoints/videos.js';
1212
import image from './endpoints/image.js';
1313
import requireAuth from './middleware/requireAuth.js';
14+
import logger from './utils/logger.js';
15+
16+
let isDevelopment = false;
1417

1518
const app = express();
1619

@@ -21,25 +24,33 @@ const options: cors.CorsOptions = {
2124
};
2225

2326
const RedisStore = connectRedis(session);
24-
const redisClient = createClient({legacyMode: true});
27+
const redisClient = createClient({
28+
legacyMode: true,
29+
url: `redis://${env.REDIS_SERVER}:${env.REDIS_PORT}`,
30+
});
31+
32+
if (env.DEVELOPMENT === 'true') {
33+
isDevelopment = true;
34+
logger.info('Development mode');
35+
}
2536

2637
redisClient.connect().then(() => {
27-
console.log('Successfully connected to redis.');
38+
logger.info('Successfully connected to redis.');
2839
}).catch((_error:any) => {
29-
console.log('Failed to connect to redis, server not running...');
40+
logger.error('Failed to connect to redis, server not running');
3041
process.exitCode = 1;
3142
process.exit();
3243
});
3344

3445
app.use(cors(options));
3546
app.use(cookieParser());
3647
app.use(session({
37-
store: new RedisStore({client: redisClient}),
48+
store: new RedisStore({client: redisClient, unref: true}),
3849
secret: env.SESSION_SECRET,
3950
resave: false,
4051
saveUninitialized: false,
4152
cookie: {
42-
secure: false, // Change in production
53+
secure: isDevelopment ? false : true,
4354
httpOnly: true,
4455
},
4556
}));
@@ -56,3 +67,4 @@ app.use('/api/videos', videos);
5667
app.use('/api/image', image);
5768

5869
export default app;
70+
export {redisClient};

src/db/mongo.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
11
import {MongoClient} from 'mongodb';
2+
import env from '..//utils/env.js';
3+
import logger from '../utils/logger.js';
24

3-
const uri = 'mongodb://localhost:27017/?readPreference=primary&ssl=false';
5+
const uri = `mongodb://${env.MONGO_USER}:${env.MONGO_PASSWORD}@${env.MONGO_HOST}:${env.MONGO_PORT}/`;
46

57
const client = new MongoClient(uri);
68

7-
let dbConnection: any;
9+
let database: any;
810

911
/**
10-
* @param {Function} callback callback function
11-
* @returns {any} Callback
12+
* Connects to mongodb server
1213
*/
13-
function connectToServer(callback: Function): any {
14-
client.connect((err, db) => {
15-
if (err || !db) {
16-
return callback(err);
17-
}
18-
19-
dbConnection = db.db('zHomeMedia');
20-
console.log('Successfully connected to MongoDB.');
21-
22-
return callback();
23-
});
24-
25-
return null;
14+
async function connect() {
15+
try {
16+
const connection = await client.connect();
17+
database = connection.db('zHomeMedia');
18+
logger.info('Connected to mongodb');
19+
} catch (e) {
20+
logger.error(`Couldn't connect to database ${e}`);
21+
}
2622
}
2723

2824
/**
2925
* @returns {any} Database connection object
3026
*/
3127
function getDb(): any {
32-
return dbConnection;
28+
return database;
29+
}
30+
31+
/**
32+
* Disconnects from mongodb server
33+
*/
34+
async function disconnect() {
35+
await client.close();
3336
}
3437

35-
export {connectToServer, getDb};
38+
export {connect, disconnect, getDb};

src/endpoints/auth.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import app, {redisClient} from '../app';
2+
import supertest from 'supertest';
3+
import * as mongodb from '../db/mongo.js';
4+
// import logger from '../utils/logger.js';
5+
6+
beforeAll(async () => {
7+
await mongodb.connect();
8+
});
9+
10+
describe('Test auth without credentials', () => {
11+
it('should return a 400 status code', async () => {
12+
const response = await supertest(app)
13+
.post('/api/auth')
14+
.expect(400);
15+
expect((response.error as any).text).toEqual('Invalid credentials');
16+
});
17+
});
18+
19+
describe('Test auth with bad credentials', () => {
20+
it('It should return a 400 status code', async () => {
21+
const response = await supertest(app)
22+
.post('/api/auth')
23+
.send({
24+
user: 'bad user',
25+
password: 'bad password',
26+
})
27+
.expect(400);
28+
expect((response.error as any).text).toEqual('Invalid credentials');
29+
});
30+
});
31+
32+
describe('Test auth with good credentials', () => {
33+
it('should return a 200 page with user info', async () => {
34+
const response = await supertest(app)
35+
.post('/api/auth')
36+
.send({
37+
user: 'admin',
38+
password: 'admin',
39+
})
40+
.expect(200);
41+
expect(response.type).toEqual('application/json');
42+
expect(response.body).toEqual({'profile': '1', 'user': 'admin'});
43+
});
44+
});
45+
46+
describe('Test logged user info when logged out', () => {
47+
it('should return a 400 status code', async () => {
48+
const response = await supertest(app)
49+
.get('/api/auth/info')
50+
.expect(400);
51+
expect((response.error as any).text).toEqual('Invalid user');
52+
});
53+
});
54+
55+
describe('Test get logged user info', () => {
56+
it('should return a 200 page with user info', async () => {
57+
let response = await supertest(app)
58+
.post('/api/auth')
59+
.send({
60+
user: 'admin',
61+
password: 'admin',
62+
})
63+
.expect(200);
64+
65+
expect(response.headers).toHaveProperty('set-cookie');
66+
const cookies = response.headers['set-cookie'].pop().split(';')[0];
67+
68+
response = await supertest(app)
69+
.get('/api/auth/info')
70+
.set('Cookie', [cookies])
71+
.expect(200);
72+
expect(response.type).toEqual('application/json');
73+
expect(response.body).toEqual({'profile': '1', 'user': 'admin'});
74+
});
75+
});
76+
77+
afterAll(async () => {
78+
await mongodb.disconnect();
79+
await redisClient.disconnect();
80+
});

0 commit comments

Comments
 (0)