Skip to content

Commit 8124714

Browse files
committed
initial commit
0 parents  commit 8124714

File tree

12 files changed

+2251
-0
lines changed

12 files changed

+2251
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
store/
3+
dist/

environment.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
declare global {
2+
namespace NodeJS {
3+
interface ProcessEnv {
4+
PORT: string;
5+
SALT_ROUNDS: string;
6+
SESSION_SECRET: string;
7+
}
8+
}
9+
}
10+
11+
export {}

package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "z-home-media-server-api",
3+
"version": "0.0.1",
4+
"private": true,
5+
"scripts": {
6+
"start": "nodemon server/server.ts"
7+
},
8+
"dependencies": {
9+
"bcrypt": "^5.0.1",
10+
"connect-redis": "^6.1.3",
11+
"cookie-parser": "^1.4.6",
12+
"cors": "^2.8.5",
13+
"dotenv": "^16.0.1",
14+
"express": "^4.18.1",
15+
"express-session": "^1.17.3",
16+
"mongodb": "4.7",
17+
"redis": "^4.1.1"
18+
},
19+
"devDependencies": {
20+
"@types/bcrypt": "^5.0.0",
21+
"@types/connect-redis": "^0.0.18",
22+
"@types/cookie-parser": "^1.4.3",
23+
"@types/cors": "^2.8.12",
24+
"@types/express": "^4.17.13",
25+
"@types/express-session": "^1.17.4",
26+
"@types/mongodb": "^4.0.7",
27+
"@types/node": "^18.0.1",
28+
"nodemon": "^2.0.16",
29+
"ts-node": "^10.8.1",
30+
"typescript": "^4.7.4"
31+
}
32+
}

server/db/mongo.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const { MongoClient } = require("mongodb");
2+
3+
const uri = "mongodb://localhost:27017/?readPreference=primary&ssl=false";
4+
5+
const client = new MongoClient(uri);
6+
7+
let dbConnection: any;
8+
9+
export function connectToServer (callback: any) {
10+
client.connect(function (err: undefined, db: { db: (arg0: string) => any; }) {
11+
if (err || !db) {
12+
return callback(err);
13+
}
14+
15+
dbConnection = db.db("zHomeMedia");
16+
console.log("Successfully connected to MongoDB.");
17+
18+
return callback();
19+
});
20+
}
21+
22+
export function getDb () {
23+
return dbConnection;
24+
}

server/endpoints/auth.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import express from 'express';
2+
import { getDb } from '../db/mongo';
3+
import dotenv from 'dotenv';
4+
import bcrypt from 'bcrypt';
5+
6+
dotenv.config();
7+
8+
const auth = express.Router();
9+
// const saltRounds = Number(process.env.SALT_ROUNDS);
10+
11+
declare module 'express-session' {
12+
interface SessionData {
13+
userId: string;
14+
user: string;
15+
}
16+
}
17+
18+
auth.post('/', function (req, res) {
19+
const { user, password, remember } = req.body;
20+
21+
if (
22+
user !== undefined &&
23+
user.length > 0 &&
24+
password !== undefined &&
25+
password.length > 0
26+
) {
27+
const db = getDb();
28+
29+
db.collection('users')
30+
.findOne({ username: user })
31+
.then((dbResult: any) => {
32+
if (dbResult !== null) {
33+
bcrypt.compare(
34+
password,
35+
dbResult.password,
36+
function (_err, authResult) {
37+
if (authResult) {
38+
req.session.userId = dbResult._id;
39+
req.session.user = dbResult.username;
40+
res.json({ user: dbResult.username });
41+
return;
42+
}
43+
44+
res.status(400).send('Invalid credentials');
45+
}
46+
);
47+
48+
return;
49+
}
50+
51+
res.status(400).send('Invalid credentials');
52+
});
53+
54+
return;
55+
}
56+
57+
res.status(400).send('Invalid credentials');
58+
});
59+
60+
auth.get('/logout', function (req, res) {
61+
req.session.destroy(() => {
62+
res.sendStatus(200);
63+
});
64+
});
65+
66+
auth.get('/info', function (req, res) {
67+
if (req.session.userId === undefined) {
68+
res.status(400).send('Invalid credentials');
69+
return;
70+
}
71+
72+
res.json({user: req.session.user});
73+
});
74+
75+
export default auth;

server/endpoints/image.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import express from 'express';
2+
import fs from 'fs';
3+
4+
const image = express.Router();
5+
6+
image.get('/image/:imageId', function (req, res) {
7+
const imageId = parseInt(req.params.imageId);
8+
let filename = imageId + '.jpg';
9+
10+
var img = fs.readFileSync('store/images/files/' + filename);
11+
res.writeHead(200, { 'Content-Type': 'image/jpg' });
12+
res.end(img, 'binary');
13+
});
14+
15+
export default image;

server/endpoints/video.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import express from 'express';
2+
import fs from 'fs';
3+
import {getDb} from '../db/mongo';
4+
5+
const video = express.Router();
6+
7+
video.get('/:videoId/info', function (req, res) {
8+
const db = getDb();
9+
10+
const uuid = req.params.videoId;
11+
12+
db.collection('videos')
13+
.findOne({ uuid: uuid })
14+
.then((result: any) => {
15+
res.json(result);
16+
});
17+
});
18+
19+
video.get('/:videoId', function (req, res) {
20+
let range = req.headers.range;
21+
22+
if (range === undefined) {
23+
res.status(400).send('Requires Range header');
24+
return;
25+
}
26+
27+
const videoId = req.params.videoId;
28+
let filename = videoId;
29+
30+
const videoPath = 'store/videos/files/' + filename;
31+
const videoSize = fs.statSync('store/videos/files/' + filename).size;
32+
const CHUNK_SIZE = 10 ** 6;
33+
const start = Number(range.replace(/\D/g, ''));
34+
const end = Math.min(start + CHUNK_SIZE, videoSize - 1);
35+
const contentLength = end - start + 1;
36+
const headers = {
37+
'Content-Range': `bytes ${start}-${end}/${videoSize}`,
38+
'Accept-Ranges': 'bytes',
39+
'Content-Length': contentLength,
40+
'Content-Type': 'video/mp4',
41+
};
42+
res.writeHead(206, headers);
43+
const videoStream = fs.createReadStream(videoPath, { start, end });
44+
videoStream.pipe(res);
45+
});
46+
47+
export default video;

server/endpoints/videos.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import express from 'express';
2+
import fs from 'fs';
3+
import { getDb } from '../db/mongo';
4+
5+
const videos = express.Router();
6+
7+
videos.get('/', function (_req, res) {
8+
const db = getDb();
9+
10+
db.collection('videos')
11+
.find({})
12+
.toArray(function (err: any, result: any) {
13+
if (err) {
14+
res.status(400).send('Error fetching listings!');
15+
} else {
16+
res.json({items: result});
17+
}
18+
});
19+
});
20+
21+
videos.get('/thumb/:videoId', function (req, res) {
22+
const videoId = req.params.videoId;
23+
let filename = videoId;
24+
25+
var img = fs.readFileSync('store/videos/thumbs/' + filename);
26+
res.writeHead(200, { 'Content-Type': 'image/png' });
27+
res.end(img, 'binary');
28+
});
29+
30+
export default videos;

server/middleware/requireAuth.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Request, Response, NextFunction } from 'express';
2+
3+
function requireAuth (req: Request, res: Response, next: NextFunction) {
4+
// console.log(req.path);
5+
// console.log(req.session);
6+
7+
if (req.path !== '/api/auth') {
8+
if (req.session.userId === undefined) {
9+
console.log('Bloqued request!');
10+
res.status(400).send('Invalid user');
11+
return;
12+
}
13+
}
14+
15+
next()
16+
}
17+
18+
export default requireAuth;

server/server.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import express from 'express';
2+
import dotenv from 'dotenv';
3+
import cors from 'cors';
4+
import { connectToServer } from './db/mongo';
5+
import cookieParser from 'cookie-parser';
6+
import auth from './endpoints/auth';
7+
import video from './endpoints/video';
8+
import videos from './endpoints/videos';
9+
import image from './endpoints/image';
10+
import requireAuth from './middleware/requireAuth';
11+
import connectRedis from 'connect-redis';
12+
import { createClient } from 'redis';
13+
import session from 'express-session';
14+
15+
const RedisStore = connectRedis(session);
16+
const redisClient = createClient({ legacyMode: true });
17+
redisClient
18+
.connect()
19+
.then(() => {
20+
console.log('redis connected');
21+
})
22+
.catch(console.error);
23+
24+
dotenv.config();
25+
26+
const app = express();
27+
const port = process.env.PORT;
28+
const allowedOrigins = ['http://localhost', 'http://localhost:8080'];
29+
30+
const options: cors.CorsOptions = {
31+
origin: allowedOrigins,
32+
credentials: true,
33+
};
34+
35+
app.use(
36+
session({
37+
store: new RedisStore({ client: redisClient }),
38+
secret: process.env.SESSION_SECRET as string,
39+
resave: false,
40+
saveUninitialized: false,
41+
cookie: {
42+
secure: false, // Change in production
43+
httpOnly: true,
44+
},
45+
})
46+
);
47+
app.use(cors(options));
48+
app.use(cookieParser());
49+
app.use(requireAuth);
50+
app.use(express.json());
51+
52+
app.use('/api/auth', auth);
53+
app.use('/api/video', video);
54+
app.use('/api/videos', videos);
55+
app.use('/api/image', image);
56+
57+
connectToServer(function (error: any) {
58+
if (error) {
59+
console.log(error);
60+
process.exit();
61+
}
62+
63+
app.listen(port, function () {
64+
console.log(`[server]: Server is running on port ${port}`);
65+
});
66+
});

tsconfig.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "CommonJS",
5+
"moduleResolution": "node",
6+
"outDir": "./dist",
7+
"esModuleInterop": true,
8+
"forceConsistentCasingInFileNames": true,
9+
"strict": true,
10+
"skipLibCheck": true
11+
},
12+
"exclude": ["node_modules", "**/node_modules/*"],
13+
}

0 commit comments

Comments
 (0)