...
 
Commits (7)
......@@ -13,7 +13,7 @@
"debug": "ts-node $NODE_DEBUG_OPTION src/index.ts -- --debug",
"qstart": "node dist/index.js",
"test": "mocha --require ts-node/register --timeout 20000 test/*.ts",
"build": "rd /s /q \"dist\" && tsc --build",
"build": "npx rimraf \"dist\" && tsc --build",
"setup": "git submodule sync --recursive && git submodule update --init --recursive && yarn install && yarn build && yarn installFrontend && yarn frontend-prod && yarn installReact && yarn buildReact",
"gitconfig": "git config diff.submodule log && git config fetch.recursesubmodules on-demand && git config status.submodulesummary true && git config push.recursesubmodules on-demand && git config submodule.recurse true",
"pull": "git pull && git submodule sync --recursive && git submodule update --init --recursive",
......@@ -119,6 +119,7 @@
"qrcode": "^1.3.3",
"randomstring": "^1.1.5",
"readline-sync": "^1.4.9",
"rimraf": "^2.6.3",
"semver": "^6.1.1",
"slugify": "^1.3.4",
"systeminformation": "^4.11.4",
......
......@@ -20,4 +20,31 @@
height: auto;
line-height: normal;
padding: 1em;
}
.UI-notification-loading .ant-spin-nested-loading {
position: fixed;
width: 100%;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(255,255,255,0.5);
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
}
.UI-notification-loading .ant-spin-container {
display: none;
}
select {
display: inline-block;
box-sizing: border-box;
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 4px;
outline: none;
padding: 4px 5px 5px;
}
\ No newline at end of file
......@@ -44,6 +44,7 @@ class App extends Component<AppProps, AppState> {
<Route path='/system/login' component={DismissMessages(import('./pages/Login'))}/>
<Route path='/system/config' component={AuthRequired(import('./pages/Config'))}/>
<Route path='/system/karas/download' component={AuthRequired(import('./pages/Karas/KaraDownload'))}/>
<Route path='/system/karas/blacklist' component={AuthRequired(import('./pages/Karas/KaraBlacklist'))}/>
<Route path='/system/karas/create' component={AuthRequired(import('./pages/Karas/KaraEdit'))}/>
<Route path='/system/karas/history' component={AuthRequired(import('./pages/Karas/History'))}/>
<Route path='/system/karas/ranking' component={AuthRequired(import('./pages/Karas/Ranking'))}/>
......
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
import {createBrowserHistory} from 'history';
import navigation from './reducers/navigation';
import auth from './reducers/auth';
......@@ -9,7 +9,7 @@ import auth from './reducers/auth';
declare var window: any;
const initialState = {};
export const history = createHistory();
export const history = createBrowserHistory();
const middleware = [routerMiddleware(history)];
// If devtools is present use it's compose instead of redux's compose; Does the same thing
......
......@@ -81,6 +81,7 @@ class KMMenu extends Component<KMenuProps, KMenuState> {
<Menu.Item key='karalist'><Link to='/system/karas'>List</Link></Menu.Item>
<Menu.Item key='karaimport'><Link to='/system/karas/create'>New</Link></Menu.Item>
<Menu.Item key='karadownload'><Link to='/system/karas/download'>Download</Link></Menu.Item>
<Menu.Item key='karablacklist'><Link to='/system/karas/blacklist'>Blacklist</Link></Menu.Item>
<Menu.Item key='karahistory'><Link to='/system/karas/history'>History</Link></Menu.Item>
<Menu.Item key='kararanking'><Link to='/system/karas/ranking'>Most requested</Link></Menu.Item>
<Menu.Item key='karaviewcounts'><Link to='/system/karas/viewcounts'>Most played</Link></Menu.Item>
......
......@@ -65,11 +65,15 @@ class Notifications extends Component<NotificationsProps, NotificationsState> {
render() {
return (
<div>
{this.info()}
{this.warn()}
{this.error()}
{this.loading()}
<div className="UI-notification">
<div className="UI-notification-message">
{this.info()}
{this.warn()}
{this.error()}
</div>
<div className="UI-notification-loading">
{this.loading()}
</div>
</div>
);
}
......
import React, {Component} from 'react';
import axios from 'axios';
import {connect} from 'react-redux';
import {Row, Col, Icon, Layout, Table, Input, InputNumber, Button, Select} from 'antd';
import {loading, errorMessage, warnMessage, infoMessage} from '../../actions/navigation';
import openSocket from 'socket.io-client';
import { getLocalKaras, deleteDownloadQueue, deleteKAraFromDownloadQueue, postToDownloadQueue, putToDownloadQueueStart, putToDownloadQueuePause } from '../../api/local';
import {ReduxMappedProps} from '../../react-app-env';
const { Option } = Select;
const encodeForm = (data) => {
return Object.keys(data)
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
.join('&');
}
interface KaraBlacklistProps extends ReduxMappedProps {}
interface KaraBlacklistState {
criterias: any[],
filter_type: number,
filter_mode: string,
filter_options: any[],
filter_options_full: any[],
filter_value: any,
utime: any,
}
class KaraBlacklist extends Component<KaraBlacklistProps, KaraBlacklistState> {
constructor(props) {
super(props);
this.state = {
criterias: [],
filter_type:1004,
filter_mode:'text',
filter_options:[],
filter_options_full:[],
filter_value:null,
utime:null,
};
}
componentDidMount() {
console.log('componentDidMount');
this.props.loading(true);
axios.get('/api/system/tags?instance=kara.moe')
.then(res => {
this.props.loading(false);
this.setState({
filter_options_full:res.data.content,
});
this.refresh();
})
.catch(err => {
this.props.loading(false);
this.props.errorMessage(`${err.response.status}: ${err.response.statusText}. ${err.response.data}`);
});
}
refresh() {
console.log('refresh');
axios.get('/api/system/downloads/blacklist/criterias')
.then(res => {
this.setState({criterias: res.data});
})
.catch(err => {
this.props.loading(false);
this.props.errorMessage(`${err.response.status}: ${err.response.statusText}. ${err.response.data}`);
});
this.setState({utime:Date.now()});
}
handleCriteriasTypeChange(value,option) {
console.log('handleCriteriasTypeChange');
var mode = option.props['data-mode'];
if(mode=='tag')
{
this.setState({
filter_type:value,
filter_mode:mode,
filter_options: this.state.filter_options_full.map((o)=>{
if(o.type==value)
return o;
}).filter((o)=>{ return o; }),
filter_value:null,
});
}
else if(mode=='number')
{
this.setState({
filter_type:value,
filter_mode:mode,
filter_value:0,
});
}
else
{
this.setState({
filter_type:value,
filter_mode:mode,
filter_value:'',
});
}
}
handleCriteriaValue(value) {
console.log('handleCriteriaValue');
if(value.target)
value = value.target.value;
this.setState({
filter_value:value,
});
}
handleCriteriaSubmit() {
console.log('handleCriteriaSubmit');
if(this.state.filter_value===null || this.state.filter_value==='')
{
this.props.errorMessage(`Critère invalide`);
return;
}
if(this.state.filter_type===1002 && this.state.filter_value==0)
{
this.props.errorMessage(`Durée minimum de 1s`);
return;
}
this.props.loading(true);
axios.post('/api/system/downloads/blacklist/criterias',encodeForm({
type:this.state.filter_type,
value:this.state.filter_value,
}))
.then(res => {
this.props.loading(false);
this.setState({
filter_type:1004,
filter_mode:'text',
filter_options:[],
filter_value:null,
});
this.refresh();
})
.catch(err => {
this.props.loading(false);
this.props.errorMessage(`${err.response.status}: ${err.response.statusText}. ${err.response.data}`);
});
}
handleCriteriaDelete(id){
console.log('handleCriteriaDelete');
this.props.loading(true);
axios.delete('/api/system/downloads/blacklist/criterias/'+id)
.then(res => {
this.props.loading(false);
this.refresh();
})
.catch(err => {
this.props.loading(false);
this.props.errorMessage(`${err.response.status}: ${err.response.statusText}. ${err.response.data}`);
});
}
filter_input() {
console.log('filter_input');
if(this.state.filter_mode=='text')
{
return <Input style={{ width:200 }} value={this.state.filter_value} onChange={this.handleCriteriaValue.bind(this)} />
}
else if(this.state.filter_mode=='number')
{
return <InputNumber value={this.state.filter_value} onChange={this.handleCriteriaValue.bind(this)} />
}
else if(this.state.filter_mode=='tag' && this.state.filter_options.length)
{
return <select
style={{ width: 200 }}
onChange={this.handleCriteriaValue.bind(this)}
>
<option key="null" value=""></option>
{this.state.filter_options.map(o => <option key={o.tag_id} value={o.tag_id}>{o.name}</option>)}
</select>
}
else
{
return null
}
}
render() {
console.log('render');
return (
<Layout.Content style={{ padding: '25px 50px', textAlign: 'center' }}>
<Layout>
<Layout.Header>
<Select style={{ width: 200 }} value={this.state.filter_type} onChange={this.handleCriteriasTypeChange.bind(this)}>
{this.criteras_types.map(o => <Option key={o.value} data-mode={o.mode} value={o.value}>{o.label}</Option>)}
</Select>
{" "}
{this.filter_input()}
{" "}
<Button type="primary" onClick={this.handleCriteriaSubmit.bind(this)}>+</Button>
</Layout.Header>
<Layout.Content>
<Table
dataSource={this.state.criterias}
columns={this.criterias_columns}
rowKey='dlblc_id'
/>
</Layout.Content>
</Layout>
</Layout.Content>
);
}
criterias_columns = [
{
title: 'Type',
dataIndex: 'type',
key: 'type',
render: type => {
var t = this.criteras_types.filter((t)=>{ return t.value==type})
return t.length>0 ? t[0].label : type;
}
}, {
title: 'Value',
dataIndex: 'value',
key: 'value',
render: (value, record) => {
var label = value
var t = this.criteras_types.filter((t)=>{ return t.mode=="tag" && t.value==record.type})
if(t.length>0) // c'est un tag ^^
{
var o = this.state.filter_options_full.filter((o) => { return o.tag_id==value})
if(o.length>0)
label = o[0].name;
}
return <span>{label} <Button type="primary" onClick={this.handleCriteriaDelete.bind(this,record.dlblc_id)}>-</Button></span>
}
},
];
criteras_types = [
{
label:"Plus long que (s)",
value:1002,
mode:'number',
},
{
label:"Plus court que (s)",
value:1003,
mode:'number',
},
{
label:"Titre contenant",
value:1004,
mode:'text',
},
{
label:"Série contenant",
value:1000,
mode:'text',
},
{
label:"Métadonnées",
value:0,
mode:'text',
},
{
label:"Chanteur",
value:2,
mode:'tag',
},
{
label:"Type",
value:3,
mode:'tag',
},
{
label:"Créateur",
value:4,
mode:'tag',
},
{
label:"Langue",
value:5,
mode:'tag',
},
{
label:"Auteur du kara",
value:6,
mode:'tag',
},
{
label:"Tags",
value:7,
mode:'tag',
},
{
label:"Compositeur",
value:8,
mode:'tag',
},
{
label:"Groupe de kara",
value:9,
mode:'tag',
}
];
}
const mapStateToProps = (state) => ({
loadingActive: state.navigation.loading
});
const mapDispatchToProps = (dispatch) => ({
loading: (active) => dispatch(loading(active)),
infoMessage: (message) => dispatch(infoMessage(message)),
errorMessage: (message) => dispatch(errorMessage(message)),
warnMessage: (message) => dispatch(warnMessage(message))
});
export default connect(mapStateToProps, mapDispatchToProps)(KaraBlacklist);
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
import {createBrowserHistory} from 'history';
import navigation from './reducers/navigation';
import auth from './reducers/auth';
const initialState = {};
export const history = createHistory();
export const history = createBrowserHistory();
const middleware = [
applyMiddleware(routerMiddleware(history))
......
This diff is collapsed.
......@@ -98,7 +98,7 @@ export default function systemDownloadController(router: Router) {
});
router.delete('/system/downloads/blacklist/criterias/:id', requireNotDemo, requireAuth, requireValidUser, requireAdmin, async (req: any, res: any) => {
try {
await removeDownloadBLC(req.params.id);
await removeDownloadBLC(parseInt(req.params.id));
res.status(200).send('Download blacklist criteria removed');
} catch(err) {
res.status(500).send(`Error removing download BLC : ${err}`);
......
import { Router } from "express";
import { requireAdmin, requireValidUser, requireAuth } from "../middlewares/auth";
import {getRemoteTags} from '../../services/download';
import { getTags } from "../../services/tag";
export default function systemTagController(router: Router) {
router.get('/system/tags', requireAuth, requireValidUser, requireAdmin, async (req: any, res: any) => {
try {
const tags = await getTags({
filter: req.query.filter,
type: req.query.type,
from: 0,
size: 999999999
});
let tags: any;
if (req.query.instance) {
tags = await getRemoteTags(req.query.instance, {
filter: req.query.filter,
type: req.query.type,
from: 0,
size: 999999999
});
} else {
tags = await getTags({
filter: req.query.filter,
type: req.query.type,
from: 0,
size: 999999999
});
}
res.json(tags);
} catch(err) {
res.status(500).send(`Error while fetching tags: ${err}`);
......
......@@ -17,6 +17,7 @@ import got from 'got';
import { QueueStatus, KaraDownload, KaraDownloadRequest, KaraDownloadBLC } from '../types/download';
import { DownloadItem } from '../types/downloader';
import { KaraList, KaraParams } from '../lib/types/kara';
import { TagParams } from '../lib/types/tag';
import { DBDownload, DBDownloadBLC } from '../types/database/download';
import { deleteKara } from '../services/kara';
import { refreshAll } from '../lib/dao/database';
......@@ -328,7 +329,7 @@ export async function editDownloadBLC(blc: KaraDownloadBLC) {
export async function removeDownloadBLC(id: number) {
const dlBLC = await selectDownloadBLC();
if (!dlBLC.some(e => e.dlblc_id === id)) throw 'DL BLC ID does not exist';
if (!dlBLC.some(e => e.dlblc_id === id )) throw 'DL BLC ID does not exist';
return await deleteDownloadBLC(id);
}
......@@ -346,6 +347,14 @@ export async function getRemoteKaras(instance: string, params: KaraParams): Prom
return JSON.parse(res.body);
}
export async function getRemoteTags(instance: string, params: TagParams): Promise<any> {
const queryParams = new URLSearchParams([
['type', params.type + '']
]);
const res = await got(`https://${instance}/api/karas/tags?${queryParams.toString()}`);
return JSON.parse(res.body);
}
export async function updateBase(instance: string) {
// This function can be improved later to take blacklist criterias into account
// Another idea would be not to download songs older than the newest song in database : for example we can assume that if a user downloaded a song added on 01/02/2019, we won't download any new songs added before that date because the user already viewed songs before that date and choose not to download them
......
......@@ -3858,7 +3858,7 @@ revalidator@0.1.x:
resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b"
integrity sha1-/s5hv6DBtSoga9axgZgYS91SOjs=
rimraf@2.6.3, rimraf@2.x.x, rimraf@^2.6.1:
rimraf@2.6.3, rimraf@2.x.x, rimraf@^2.6.1, rimraf@^2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
......