lots...
This commit is contained in:
7
models/index.js
Normal file
7
models/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
User: require('./ldap/user').User,
|
||||
AuthToken: require('./sql').AuthToken,
|
||||
Torrent: require('./sql').Torrent
|
||||
};
|
99
models/ldap/group.js
Normal file
99
models/ldap/group.js
Normal file
@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
const { Client, Attribute, Change } = require('ldapts');
|
||||
const conf = require('>/conf').ldap;
|
||||
|
||||
const client = new Client({
|
||||
url: conf.url,
|
||||
});
|
||||
|
||||
async function getGroups(client, member){
|
||||
try{
|
||||
|
||||
let memberFilter = member ? `(member=${member})`: ''
|
||||
|
||||
let groups = (await client.search(conf.groupBase, {
|
||||
scope: 'sub',
|
||||
filter: `(&(objectClass=groupOfNames)${memberFilter})`,
|
||||
attributes: ['*', 'createTimestamp', 'modifyTimestamp'],
|
||||
})).searchEntries;
|
||||
|
||||
return groups.map(function(group){
|
||||
if(!Array.isArray(group.member)) group.member = [group.member];
|
||||
return group
|
||||
});
|
||||
}catch(error){
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
var Group = {};
|
||||
|
||||
Group.list = async function(member){
|
||||
try{
|
||||
await client.bind(conf.bindDN, conf.bindPassword);
|
||||
|
||||
let groups = await getGroups(client, member)
|
||||
|
||||
await client.unbind();
|
||||
|
||||
return groups.map(group => group.cn);
|
||||
}catch(error){
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Group.listDetail = async function(member){
|
||||
try{
|
||||
await client.bind(conf.bindDN, conf.bindPassword);
|
||||
|
||||
let groups = await getGroups(client, member)
|
||||
|
||||
await client.unbind();
|
||||
|
||||
|
||||
return groups;
|
||||
}catch(error){
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Group.get = async function(data){
|
||||
try{
|
||||
|
||||
if(typeof data !== 'object'){
|
||||
let name = data;
|
||||
data = {};
|
||||
data.name = name;
|
||||
}
|
||||
|
||||
await client.bind(conf.bindDN, conf.bindPassword);
|
||||
|
||||
let group = (await client.search(conf.groupBase, {
|
||||
scope: 'sub',
|
||||
filter: `(&(objectClass=groupOfNames)(cn=${data.name}))`,
|
||||
attributes: ['*', 'createTimestamp', 'modifyTimestamp'],
|
||||
})).searchEntries[0];
|
||||
|
||||
await client.unbind();
|
||||
|
||||
if(!Array.isArray(group.member)) group.member = [group.member];
|
||||
|
||||
if(group){
|
||||
let obj = Object.create(this);
|
||||
Object.assign(obj, group);
|
||||
|
||||
return obj;
|
||||
}else{
|
||||
let error = new Error('GroupNotFound');
|
||||
error.name = 'GroupNotFound';
|
||||
error.message = `LDAP:${data.cn} does not exists`;
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
}catch(error){
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {Group};
|
6
models/ldap/index.js
Normal file
6
models/ldap/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
User: require('./user').User,
|
||||
Group: require('./group').Group,
|
||||
};
|
159
models/ldap/user.js
Normal file
159
models/ldap/user.js
Normal file
@ -0,0 +1,159 @@
|
||||
'use strict';
|
||||
|
||||
const {Client, Attribute} = require('ldapts');
|
||||
const LRUCache = require('lru-native2');
|
||||
const conf = require('>/conf').ldap;
|
||||
|
||||
var userLUR = new LRUCache({
|
||||
// The maximum age (in milliseconds) of an item.
|
||||
// The item will be removed if get() is called and the item is too old.
|
||||
// Default: 0, meaning items will never expire.
|
||||
maxAge: 60000,
|
||||
});
|
||||
|
||||
const client = new Client({
|
||||
url: conf.url,
|
||||
});
|
||||
|
||||
const user_parse = function(data){
|
||||
if(data[conf.userNameAttribute]){
|
||||
data.username = data[conf.userNameAttribute];
|
||||
data.userPassword = undefined;
|
||||
data.userBacking = "LDAP";
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
var User = {}
|
||||
|
||||
User.backing = "LDAP";
|
||||
|
||||
User.list = async function(){
|
||||
try{
|
||||
await client.bind(conf.bindDN, conf.bindPassword);
|
||||
|
||||
const res = await client.search(conf.userBase, {
|
||||
scope: 'sub',
|
||||
filter: conf.userFilter,
|
||||
attributes: ['*', 'createTimestamp', 'modifyTimestamp'],
|
||||
});
|
||||
|
||||
await client.unbind();
|
||||
|
||||
return res.searchEntries.map(function(user){return user.uid});
|
||||
}catch(error){
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
User.listDetail = async function(){
|
||||
try{
|
||||
await client.bind(conf.bindDN, conf.bindPassword);
|
||||
|
||||
const res = await client.search(conf.userBase, {
|
||||
scope: 'sub',
|
||||
filter: conf.userFilter,
|
||||
attributes: ['*', 'createTimestamp', 'modifyTimestamp'],
|
||||
});
|
||||
|
||||
await client.unbind();
|
||||
|
||||
let users = [];
|
||||
|
||||
for(let user of res.searchEntries){
|
||||
let obj = Object.create(this);
|
||||
Object.assign(obj, user_parse(user));
|
||||
users.push(obj);
|
||||
}
|
||||
|
||||
return users;
|
||||
|
||||
}catch(error){
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
User.get = async function(data, key){
|
||||
try{
|
||||
if(typeof data !== 'object'){
|
||||
let uid = data;
|
||||
data = {};
|
||||
data.uid = uid;
|
||||
}
|
||||
|
||||
data.searchKey = data.searchKey || key || conf.userNameAttribute;
|
||||
data.searchValue = data.searchValue || data.uid;
|
||||
|
||||
let filter = `(&${conf.userFilter}(${data.searchKey}=${data.searchValue}))`;
|
||||
if(userLUR.get(filter)) return userLUR.get(filter)
|
||||
|
||||
await client.bind(conf.bindDN, conf.bindPassword);
|
||||
const res = await client.search(conf.userBase, {
|
||||
scope: 'sub',
|
||||
filter: filter,
|
||||
attributes: ['*', 'createTimestamp', 'modifyTimestamp'],
|
||||
});
|
||||
|
||||
await client.unbind();
|
||||
|
||||
if(res.searchEntries[0]){
|
||||
let obj = Object.create(this);
|
||||
Object.assign(obj, user_parse(res.searchEntries[0]));
|
||||
|
||||
userLUR.set(filter, obj);
|
||||
return obj;
|
||||
}else{
|
||||
let error = new Error('UserNotFound');
|
||||
error.name = 'UserNotFound';
|
||||
error.message = `LDAP:${data.searchValue} does not exists`;
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
}catch(error){
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
User.exists = async function(data, key){
|
||||
// Return true or false if the requested entry exists ignoring error's.
|
||||
try{
|
||||
await this.get(data, key);
|
||||
|
||||
return true
|
||||
}catch(error){
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
User.login = async function(data){
|
||||
try{
|
||||
|
||||
let user = await this.get(data.uid || data[conf.userNameAttribute] || data.username);
|
||||
|
||||
await client.bind(user.dn, data.password);
|
||||
|
||||
await client.unbind();
|
||||
|
||||
return user;
|
||||
|
||||
}catch(error){
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = {User};
|
||||
|
||||
// (async function(){
|
||||
// try{
|
||||
// console.log(await User.list());
|
||||
|
||||
// console.log(await User.listDetail());
|
||||
|
||||
// console.log(await User.get('wmantly'))
|
||||
|
||||
// }catch(error){
|
||||
// console.error(error)
|
||||
// }
|
||||
// })()
|
49
models/sql/authtoken.js
Normal file
49
models/sql/authtoken.js
Normal file
@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (sequelize, DataTypes, Model) => {
|
||||
class AuthToken extends Model {
|
||||
/**
|
||||
* Helper method for defining associations.
|
||||
* This method is not a part of Sequelize lifecycle.
|
||||
* The `models/index` file will call this method automatically.
|
||||
*/
|
||||
static associate(models) {
|
||||
}
|
||||
|
||||
check(){
|
||||
// check expires_on date
|
||||
return this.is_valid;
|
||||
}
|
||||
}
|
||||
AuthToken.init({
|
||||
token:{
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
expires_on: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
validate:{
|
||||
isDate:true
|
||||
}
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
ldapModel: 'User',
|
||||
allowNull: false,
|
||||
validate:{
|
||||
notNull: true,
|
||||
},
|
||||
},
|
||||
is_valid: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'AuthToken',
|
||||
});
|
||||
return AuthToken;
|
||||
};
|
3
models/sql/config/config.js
Normal file
3
models/sql/config/config.js
Normal file
@ -0,0 +1,3 @@
|
||||
const conf = require('../../../conf');
|
||||
|
||||
module.exports = conf.sql;
|
109
models/sql/index.js
Normal file
109
models/sql/index.js
Normal file
@ -0,0 +1,109 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { Sequelize, Utils } = require('sequelize');
|
||||
const process = require('process');
|
||||
const basename = path.basename(__filename);
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const config = require(__dirname + '/config/config.js');
|
||||
const db = {};
|
||||
|
||||
let sequelize;
|
||||
|
||||
// Connect sequelize models to LDAP models
|
||||
const ldapModels = require('../ldap');
|
||||
|
||||
function getFieldWithLdap(attributes){
|
||||
let out = [];
|
||||
|
||||
for (const [attribute, options] of Object.entries(attributes)) {
|
||||
if(options.ldapModel) out.push(attribute)
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const sequilze_conf = {
|
||||
define: {
|
||||
hooks: {
|
||||
async afterValidate(instance) {
|
||||
let hasError = false;
|
||||
function itemError(key, validator, message){
|
||||
let error = new Sequelize.ValidationErrorItem(message);
|
||||
error.type = 'Validation error';
|
||||
error.path = key;
|
||||
error.origin = 'FUNCTION';
|
||||
error.instance = instance;
|
||||
error.validatorKey = 'validator';
|
||||
error.validatorName = 'validator';
|
||||
error.validatorArgs = [];
|
||||
error.original = [];
|
||||
|
||||
throw new Sequelize.ValidationError(null, [error]);
|
||||
}
|
||||
|
||||
for(let attribute of getFieldWithLdap(this.getAttributes())){
|
||||
let externalModel = ldapModels[this.getAttributes()[attribute].ldapModel];
|
||||
|
||||
if(!externalModel) itemError(attribute, 'modelExists', `LDAP model ${this.getAttributes()[attribute].ldapModel} not found.`);
|
||||
|
||||
if(!hasError && !(await externalModel.exists(instance[attribute])) ) itemError(attribute, 'foreignKey', `LDAP model has no object ${instance[attribute]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Model extends Sequelize.Model{
|
||||
constructor(...args){
|
||||
super(...args)
|
||||
let hasLdap = getFieldWithLdap(this.constructor.getAttributes())
|
||||
for(let attribute of hasLdap){
|
||||
let externalModelName = this.constructor.getAttributes()[attribute].ldapModel;
|
||||
this[`get${externalModelName}`] = async function(){
|
||||
return await ldapModels[externalModelName].get(this[attribute]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backward compatible with my LDAP and Redis models.
|
||||
// I will update the LDAP and Redis stuff to have method interfaces inline with sequilze
|
||||
static async get(token){
|
||||
return await this.findByPk(token);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (config.use_env_variable) {
|
||||
sequelize = new Sequelize(process.env[config.use_env_variable], config);
|
||||
} else {
|
||||
sequelize = new Sequelize(config.database, config.username, config.password, {...config, ...sequilze_conf});
|
||||
}
|
||||
|
||||
|
||||
fs
|
||||
.readdirSync(__dirname)
|
||||
.filter(file => {
|
||||
return (
|
||||
file.indexOf('.') !== 0 &&
|
||||
file !== basename &&
|
||||
file.slice(-3) === '.js' &&
|
||||
file.indexOf('.test.js') === -1
|
||||
);
|
||||
})
|
||||
.forEach(file => {
|
||||
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes, _Model);
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
db.sequelize = sequelize;
|
||||
db.Sequelize = Sequelize;
|
||||
|
||||
module.exports = db;
|
38
models/sql/migrations/20231230031017-create-auth-token.js
Normal file
38
models/sql/migrations/20231230031017-create-auth-token.js
Normal file
@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('AuthTokens', {
|
||||
token: {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
is_valid: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: true,
|
||||
allowNull: false,
|
||||
},
|
||||
username: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
expires_on: {
|
||||
allowNull: true,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
}
|
||||
});
|
||||
},
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('AuthTokens');
|
||||
}
|
||||
};
|
63
models/sql/migrations/20240101174644-create-torrent.js
Normal file
63
models/sql/migrations/20240101174644-create-torrent.js
Normal file
@ -0,0 +1,63 @@
|
||||
'use strict';
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('Torrents', {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: Sequelize.INTEGER
|
||||
},
|
||||
torrent_id: {
|
||||
type: Sequelize.STRING
|
||||
},
|
||||
hashString: {
|
||||
type: Sequelize.STRING
|
||||
},
|
||||
magnetLink: {
|
||||
type: Sequelize.STRING
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.NUMBER
|
||||
},
|
||||
isPrivate: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false,
|
||||
},
|
||||
percentDone: {
|
||||
type: Sequelize.FLOAT
|
||||
},
|
||||
errorString: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
downloadDir: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
sizeWhenDone: {
|
||||
type: Sequelize.NUMBER,
|
||||
allowNull: true
|
||||
},
|
||||
added_by: {
|
||||
type: Sequelize.STRING
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
}
|
||||
});
|
||||
},
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('Torrents');
|
||||
}
|
||||
};
|
126
models/sql/torrent.js
Normal file
126
models/sql/torrent.js
Normal file
@ -0,0 +1,126 @@
|
||||
'use strict';
|
||||
|
||||
const Transmission = require('transmission-promise');
|
||||
const conf = require('>/conf');
|
||||
|
||||
const tr_client = new Transmission(conf.transmission)
|
||||
|
||||
const statusMap = [
|
||||
'STOPPED',
|
||||
'CHECK_WAIT',
|
||||
'CHECK',
|
||||
'DOWNLOAD_WAIT',
|
||||
'DOWNLOAD',
|
||||
'SEED_WAIT',
|
||||
'SEED',
|
||||
'ISOLATED',
|
||||
];
|
||||
|
||||
module.exports = (sequelize, DataTypes, Model) => {
|
||||
class Torrent extends Model {
|
||||
/**
|
||||
* Helper method for defining associations.
|
||||
* This method is not a part of Sequelize lifecycle.
|
||||
* The `models/index` file will call this method automatically.
|
||||
*/
|
||||
static associate(models) {
|
||||
// define association here
|
||||
}
|
||||
|
||||
static trClient = tr_client;
|
||||
|
||||
static async create(data, ...args){
|
||||
try{
|
||||
|
||||
// let instance = this.build(data);
|
||||
// console.log('instance', instance)
|
||||
await this.build(data).validate();
|
||||
// console.log('validate', val);
|
||||
|
||||
let res = await tr_client.addUrl(data.magnetLink);
|
||||
|
||||
return await super.create({
|
||||
magnetLink: data.magnetLink,
|
||||
torrent_id: res.id,
|
||||
hashString: res.hashString,
|
||||
name: res.name,
|
||||
added_by: data.added_by,
|
||||
status: 0,
|
||||
percentDone: 0,
|
||||
}, args);
|
||||
}catch (error){
|
||||
// console.log('Torrent create error', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getTorrentData(noUpdate){
|
||||
try{
|
||||
let res = ( await tr_client.get(Number(this.torrent_id), [
|
||||
"eta", "percentDone", "status", "rateDownload",
|
||||
"errorString", "hashString", 'name',
|
||||
'downloadDir',
|
||||
'files', //array of files
|
||||
'filesStats', // array of files with status
|
||||
'isFinished',
|
||||
'isStalled',
|
||||
'peers',
|
||||
'peersConnected', // array of peers,
|
||||
'sizeWhenDone',
|
||||
]) ).torrents[0];
|
||||
|
||||
await this.update(res);
|
||||
if(noUpdate) await this.save();
|
||||
return {...res, ...this.dataValues};
|
||||
}catch(error){
|
||||
console.error(`Torrent ${this.id} getTorrentData error`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Torrent.init({
|
||||
torrent_id: DataTypes.STRING,
|
||||
magnetLink: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
validate:{
|
||||
notNull: true,
|
||||
notEmpty: true,
|
||||
},
|
||||
},
|
||||
isPrivate: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
},
|
||||
hashString: DataTypes.STRING,
|
||||
name: DataTypes.STRING,
|
||||
added_by: {
|
||||
type: DataTypes.STRING,
|
||||
ldapModel: 'User',
|
||||
allowNull: false,
|
||||
validate:{
|
||||
notNull: true,
|
||||
notEmpty: true,
|
||||
},
|
||||
},
|
||||
status: DataTypes.NUMBER,
|
||||
percentDone: DataTypes.FLOAT,
|
||||
downloadDir: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
errorString: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
sizeWhenDone: {
|
||||
type: DataTypes.NUMBER,
|
||||
allowNull: true,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Torrent',
|
||||
logging: false,
|
||||
});
|
||||
return Torrent;
|
||||
};
|
Reference in New Issue
Block a user