server/services/database/index.js
Artem Anufrij cff48aaada
All checks were successful
continuous-integration/drone Build is passing
move
2023-02-08 12:30:56 +01:00

735 lines
19 KiB
JavaScript

const { ObjectId } = require('mongodb');
const bcrypt = require("bcryptjs");
const ffmpeg = require("fluent-ffmpeg");
const fs = require("fs");
const notifier = require("../notifier");
const server = require("../../server");
const config = server.config;
const connector = require("./CONNECTOR");
var dbo;
connector.connect().then((ret) => {
dbo = ret;
});
notifier.on("metadata_excluded", item => {
insert_artist_if_not_exists(item, artist => {
insert_album_if_not_exists(item, artist, album => {
insert_track_if_not_exists(item, album);
});
});
});
notifier.on("pathdata_excluded", item => {
insert_box_if_not_exists(item, box => {
insert_video_if_not_exists(item, box);
});
});
notifier.on("check_video_details", item => {
if (!item.tracks) {
get_video_tracks(item, tracks => {
item.tracks = tracks;
update_video(item);
});
}
if (!item.thumbnail) {
get_video_thumbnail(item, thumbnail => {
item.thumbnail = thumbnail;
update_video(item);
});
}
});
/*
MODULES
*/
const albums = require("./albums");
exports.albums = albums;
const radios = require("./radios");
exports.radios = radios;
const tracks = require("./tracks");
exports.tracks = tracks;
const artists = require("./artists");
exports.artists = artists;
const boxes = require("./boxes");
exports.boxes = boxes;
const videos = require("./videos");
exports.videos = videos;
const users = require("./users");
exports.users = users;
const system = require("./system");
exports.system = system;
exports.artist_count = function (callback) {
return dbo.collection("artists").countDocuments(callback);
};
exports.album_count = function (callback) {
return dbo.collection("albums").countDocuments(callback);
};
exports.track_count = function (callback) {
return dbo.collection("tracks").countDocuments(callback);
};
exports.box_count = function (callback) {
return dbo.collection("boxes").countDocuments(callback);
};
exports.video_count = function (callback) {
return dbo.collection("videos").countDocuments(callback);
};
exports.users_count = function (callback) {
return dbo.collection("users").countDocuments(callback);
};
/*
USER
*/
exports.addUser = function (user, callback) {
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(user.password, salt, function (err, hash) {
user.password = hash;
if (!user.roles) {
user.roles = ["user"];
}
user.name = user.name.toLowerCase();
user.player = {};
user.fullname = user.name;
dbo.collection("users").insertOne(user, err => {
if (err) throw err;
dbo.collection("users").findOne({ name: user.name }, (err, result) => {
if (err) throw err;
delete result.password;
callback(result);
});
});
});
});
};
exports.deleteUser = function (id, callback) {
dbo
.collection("users")
.deleteOne({ _id: ObjectId(id) }, (err, result) => {
if (err) throw err;
callback(result);
});
};
exports.updateUserRole = function (user, callback) {
dbo
.collection("users")
.updateOne({ name: user.name }, { $set: { roles: user.roles } }, err => {
if (err) throw err;
callback();
});
};
exports.updateUserAccess = function (name) {
dbo
.collection("users")
.updateOne(
{ name: name },
{ $set: { last_access: new Date().toLocaleString() } }
);
};
exports.updateUserPassword = function (user, newPassowrd) {
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(newPassowrd, salt, function (err, hash) {
dbo
.collection("users")
.updateOne({ name: user }, { $set: { password: hash } },
err => { if (err) throw err; });
});
});
};
exports.updateUserConfig = function (user, body, callback) {
dbo.collection("users").updateOne(
{ name: user.name },
{
$set: {
mobile_bpm: body.mobile_bpm,
desktop_bpm: body.desktop_bpm,
video_lang: body.video_lang,
video_quality: body.video_quality,
fullname: body.fullname
}
},
err => {
if (err) throw err;
if (callback) {
callback();
}
}
);
};
exports.updateUserSettings = function (user, callback) {
dbo
.collection("users")
.updateOne(
{ _id: user._id },
{ $set: { player: user.player } },
err => {
if (err) throw err;
if (callback) {
callback();
}
}
);
};
exports.userByName = function (name, callback) {
dbo.collection("users").findOne({ name: name.toLowerCase() }, (err, result) => {
if (err) throw err;
callback(result);
});
};
exports.userById = function (id, callback) {
dbo
.collection("users")
.findOne({ _id: ObjectId(id) }, (err, result) => {
if (err) throw err;
callback(result);
});
};
/*
HISTORY
*/
exports.historyList = function (id, callback) {
dbo
.collection("history")
.find(
{ userId: id, type: { $in: ['album', 'artist', 'box', 'radio'] } },
{ projection: { _id: false, userId: false } }
)
.sort({ _id: -1 })
.limit(12)
.toArray((err, result) => {
if (err) throw err;
callback(result);
});
};
exports.clearHistory = function (id, callback) {
dbo
.collection("history")
.deleteMany({ userId: id }, (err, result) => {
if (err) throw err;
callback(result);
});
};
exports.updateHistory = function (item, callback) {
dbo
.collection("history")
.deleteMany({ userId: item.userId, id: item.id, type: { $in: ['album', 'artist', 'box', 'radio'] } }, err => {
if (err) throw err;
dbo
.collection("history")
.insertOne(item, err => {
if (err) throw err;
callback();
}
);
});
};
exports.search = function (term, callback) {
let r = [];
dbo
.collection("artists")
.find(
{ name: { $regex: term, $options: "i" } },
{ projection: { _id: true, name: true, "covers.cover256": true } }
)
.toArray((err, result) => {
if (result && result.length > 0) {
result.forEach(item => {
item.type = "artist";
item.search_rank = item.name.toLowerCase().indexOf(term);
});
r = r.concat(result);
}
dbo
.collection("albums")
.find(
{ title: { $regex: term, $options: "i" } },
{ projection: { _id: true, title: true, "covers.cover128": true } }
)
.toArray((err, result) => {
if (result && result.length > 0) {
result.forEach(item => {
item.type = "album";
item.parent = {};
item.search_rank = item.title.toLowerCase().indexOf(term);
});
r = r.concat(result);
}
dbo
.collection("tracks")
.aggregate([
{
$lookup: {
from: "albums",
localField: "album_id",
foreignField: "_id",
as: "parent"
}
}, {
$unwind: "$parent"
}, {
$project: {
album_id: false,
bitrate: false,
disk: false,
duration: false,
mime: false,
path: false,
genre: false,
track: false,
"parent.artist_id": false,
"parent.year": false,
"parent.covers.cover32": false,
"parent.covers.cover128": false,
"parent.covers.cover256": false,
"parent.covers.cover512": false
}
}, {
$match: { title: { $regex: term, $options: "i" } }
}])
.toArray((err, result) => {
if (result && result.length > 0) {
result.forEach(item => {
item.type = "track";
item.search_rank = item.title.toLowerCase().indexOf(term);
});
r = r.concat(result);
}
dbo
.collection("boxes")
.find(
{ title: { $regex: term, $options: "i" } },
{ projection: { _id: true, title: true, "covers.cover128": true } }
)
.toArray((err, result) => {
if (result && result.length > 0) {
result.forEach(item => {
item.type = "box";
item.search_rank = item.title.toLowerCase().indexOf(term);
});
r = r.concat(result);
}
dbo
.collection("videos")
.aggregate([
{
$lookup: {
from: "boxes",
localField: "box_id",
foreignField: "_id",
as: "parent"
}
}, {
$unwind: "$parent"
}, {
$project: {
path: false,
mime: false,
tracks: false,
box_id: false,
"parent.box_id": false,
"parent.year": false,
"parent.path": false,
"parent.title": false,
"parent.covers.cover32": false,
"parent.covers.cover64": false,
"parent.covers.cover128": false,
"parent.covers.cover256": false,
"parent.covers.cover512": false
}
}, {
$match: { title: { $regex: term, $options: "i" } }
}]
)
.toArray((err, result) => {
if (result && result.length > 0) {
result.forEach(item => {
item.type = "video";
item.search_rank = item.title
.toLowerCase()
.indexOf(term);
});
r = r.concat(result);
}
dbo
.collection("radios")
.find(
{ name: { $regex: term, $options: "i" } },
{ projection: { _id: true, name: true } }
)
.toArray((err, result) => {
if (result && result.length > 0) {
result.forEach(item => {
item.type = "radio";
item.search_rank = item.name
.toLowerCase()
.indexOf(term);
});
r = r.concat(result);
}
callback(
r
.sort((a, b) => {
return a.search_rank - b.search_rank;
})
.splice(0, 50)
);
});
});
});
});
});
});
};
/*
CHAT
*/
exports.messageList = function (callback) {
dbo
.collection("chat")
.aggregate([
{
$lookup: {
from: "users",
localField: "user_id",
foreignField: "_id",
as: "user"
}
},
{
$project: {
"user.password": false,
"user.player": false
}
},
{ $unwind: "$user" },
{ $sort: { _id: 1 } }
])
.toArray((err, result) => {
if (err) throw err;
callback(result.splice(0, 100));
});
};
exports.writeMessage = function (user, message, callback) {
dbo
.collection("chat")
.insertOne(
{ user_id: user._id, message: message, timestamp: Date.now() },
(err, result) => {
if (err) throw err;
callback(result.ops);
}
);
};
exports.deleteMessage = function (id, user, callback) {
dbo.collection("chat").deleteOne({ _id: ObjectId(id), user_id: user._id }, err => {
if (err) throw err;
callback();
});
};
/*
FUNCTIONS
*/
function insert_artist_if_not_exists(item, callback) {
dbo.collection("artists").findOne({ name: item.artist }, (err, result) => {
if (err) throw err;
if (result) {
callback(result);
} else {
let artist = {
name: item.artist,
insert_on: Date.now()
}
if (item.owner) {
artist.owner_id = item.owner._id;
}
dbo
.collection("artists")
.updateOne(
{ name: item.artist },
{ $set: artist },
{ upsert: true },
err => {
if (err) throw err;
insert_artist_if_not_exists(item, callback);
}
);
}
});
}
function insert_album_if_not_exists(item, artist, callback) {
dbo
.collection("albums")
.findOne({ artist_id: artist._id, title: item.album }, (err, result) => {
if (err) throw err;
if (result) {
callback(result);
} else {
let album = {
artist_id: artist._id,
artist_name: artist.name,
title: item.album.trim(),
year: item.year,
insert_on: Date.now()
}
if (item.covers) {
album.cover32 = item.covers.cover32;
album.cover64 = item.covers.cover64;
album.cover128 = item.covers.cover128;
album.cover256 = item.covers.cover256;
}
if (item.owner) {
album.owner_id = item.owner._id;
}
dbo.collection("albums").updateOne(
{ artist_id: artist._id, title: item.album },
{ $set: album },
{ upsert: true },
err => {
if (err) throw err;
insert_album_if_not_exists(item, artist, callback);
}
);
}
});
}
function insert_track_if_not_exists(item, album, callback) {
let short_path = ""
if (item.path.indexOf(config.upload_folder) == 0) {
short_path = item.path.replace(config.upload_folder, "");
} else {
short_path = item.path.replace(config.music_folder, "");
}
dbo.collection("tracks").updateOne(
{ path: short_path },
{
$set: {
title: item.title,
path: short_path,
album_id: album._id,
duration: item.duration,
bitrate: item.bitrate,
mime: item.mime,
disk: item.disk,
track: item.track,
genre: item.genre,
insert_on: Date.now()
}
},
{ upsert: true },
err => {
if (err) throw err;
dbo.collection("tracks").findOne({ path: short_path }, (err, result) => {
if (err) throw err;
if (callback) {
callback(result);
}
});
}
);
}
function insert_box_if_not_exists(item, callback) {
let short_path = item.box_path.replace(config.video_folder, "");
dbo
.collection("boxes")
.findOne({ title: item.box, year: item.year }, (err, result) => {
if (err) throw err;
if (result) {
callback(result);
} else {
let box = {
title: item.box,
path: short_path,
year: item.year,
cover64: item.cover64,
cover128: item.cover128,
cover256: item.cover256,
cover512: item.cover512,
insert_on: Date.now()
}
if (item.owner) {
box.owner_id = item.owner._id;
}
dbo
.collection("boxes")
.updateOne(
{ title: item.box },
{ $set: box },
{ upsert: true },
err => {
if (err) throw err;
insert_box_if_not_exists(item, callback);
}
);
}
});
}
function insert_video_if_not_exists(item, box, callback) {
item.path = item.path.replace(config.video_folder, "");
dbo.collection("videos").updateOne(
{ path: item.path },
{
$set: {
title: item.title,
path: item.path,
box_id: box._id,
mime: item.mime,
insert_on: Date.now()
}
},
{ upsert: true },
err => {
if (err) throw err;
dbo.collection("videos").findOne({ path: item.path }, (err, result) => {
if (err) throw err;
if (callback) {
callback(result);
}
if (!result.tracks) {
get_video_tracks(item, tracks => {
result.tracks = tracks;
update_video(result);
});
}
if (!result.thumbnail) {
get_video_thumbnail(result, thumbnail => {
result.thumbnail = thumbnail;
update_video(result);
});
}
});
}
);
}
function update_video(video, callback) {
process.stdout.write("updating video: " + video.title + "\n");
let val = {};
if (video.tracks) {
val.tracks = video.tracks;
}
if (video.thumbnail) {
val.thumbnail = video.thumbnail;
}
if (!val) {
if (callback) {
callback();
}
return;
}
dbo
.collection("videos")
.updateOne({ _id: video._id }, { $set: val }, { upsert: false }, err => {
if (err) throw err;
if (callback) {
callback();
}
});
}
function get_video_thumbnail(video, callback) {
let path = config.video_folder + video.path;
if (!fs.existsSync(path)) {
path = config.upload_folder + video.path;
}
ffmpeg(path)
.on("end", () => {
process.stdout.write("Thumbnail for '" + video.title + "' created\n");
let thumbnail =
config.cache_folder + "/video_covers/" + video._id.toString() + ".png";
if (!fs.existsSync(thumbnail)) {
callback(null);
return;
}
let bitmap = fs.readFileSync(thumbnail);
callback("data:image/png;base64, " + bitmap.toString("base64"));
fs.unlinkSync(thumbnail);
})
.on("error", err => {
process.stdout.write(
"Thumbnail error for " + video.title + ": " + err.message + "\n"
);
callback(null);
})
.screenshot({
timestamps: ["20%"],
filename: video._id.toString(),
folder: config.cache_folder + "/video_covers",
size: "346x180"
});
}
function get_video_tracks(video, callback) {
let path = config.video_folder + video.path;
if (!fs.existsSync(path)) {
path = config.upload_folder + video.path;
}
ffmpeg(path).ffprobe((err, data) => {
if (!data) {
return callback([]);
}
let audio_streams = data.streams.filter(f => f.codec_type == "audio");
let return_value = audio_streams.map(m => {
return {
index: m.index,
lang: (m.tags && m.tags.language
? m.tags.language
: m.index
? m.index
: ""
).toString(),
title: (m.tags && m.tags.title
? m.tags.title
: m.index
? m.index
: ""
).toString()
};
});
callback(return_value);
});
}