diff --git a/client/src/App.tsx b/client/src/App.tsx
index 075a107..08a6107 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -4,11 +4,14 @@ import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import MainWindow from './components/MainWindow';
+import { ProvideAuth } from './lib/useAuth';
function App() {
return (
-
+
+
+
);
}
diff --git a/client/src/api.ts b/client/src/api.ts
index 7811d5d..d1cb334 100644
--- a/client/src/api.ts
+++ b/client/src/api.ts
@@ -318,4 +318,42 @@ export interface MergeTagRequest { }
export interface MergeTagResponse { }
export function checkMergeTagRequest(req: any): boolean {
return true;
-}
\ No newline at end of file
+}
+
+// Register a user (POST).
+// TODO: add e-mail verification.
+export const RegisterUserEndpoint = '/register';
+export interface RegisterUserRequest {
+ email: string,
+ password: string,
+}
+export interface RegisterUserResponse { }
+export function checkPassword(password: string): boolean {
+ const result = (password.length < 32) &&
+ (password.length >= 8) &&
+ password.split("").every(char => char.charCodeAt(0) <= 127) && // is ASCII
+ (/[a-z]/g.test(password)) && // has lowercase
+ (/[A-Z]/g.test(password)) && // has uppercase
+ (/[0-9]/g.test(password)) && // has number
+ (/[!@#$%^&*()_+/]/g.test(password)) // has special character;
+
+ console.log("Password check for ", password, ": ", result);
+ return result;
+}
+export function checkEmail(email: string): boolean {
+ const re = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
+ const result = re.test(String(email).toLowerCase());
+ console.log("Email check for ", email, ": ", result);
+ return result;
+}
+export function checkRegisterUserRequest(req: any): boolean {
+ return "body" in req &&
+ "email" in req.body &&
+ "password" in req.body &&
+ checkEmail(req.body.email) &&
+ checkPassword(req.body.password);
+}
+
+// Note: Login is handled by Passport.js, so it is not explicitly written here.
+export const LoginEndpoint = "/login";
+export const LogoutEndpoint = "/logout";
\ No newline at end of file
diff --git a/client/src/components/MainWindow.tsx b/client/src/components/MainWindow.tsx
index 2839106..2241615 100644
--- a/client/src/components/MainWindow.tsx
+++ b/client/src/components/MainWindow.tsx
@@ -1,16 +1,17 @@
-import React, { useReducer, Reducer, useContext } from 'react';
+import React from 'react';
import { ThemeProvider, CssBaseline, createMuiTheme } from '@material-ui/core';
import { grey } from '@material-ui/core/colors';
import AppBar, { AppBarTab } from './appbar/AppBar';
-import QueryWindow, { QueryWindowReducer } from './windows/query/QueryWindow';
-import { newWindowState, newWindowReducer, WindowType } from './windows/Windows';
+import QueryWindow from './windows/query/QueryWindow';
import ArtistWindow from './windows/artist/ArtistWindow';
import AlbumWindow from './windows/album/AlbumWindow';
import TagWindow from './windows/tag/TagWindow';
import SongWindow from './windows/song/SongWindow';
import ManageTagsWindow from './windows/manage_tags/ManageTagsWindow';
-import { BrowserRouter, Switch, Route, useParams, Redirect } from 'react-router-dom';
-var _ = require('lodash');
+import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom';
+import LoginWindow from './windows/login/LoginWindow';
+import { useAuth } from '../lib/useAuth';
+import RegisterWindow from './windows/register/RegisterWindow';
const darkTheme = createMuiTheme({
palette: {
@@ -21,6 +22,25 @@ const darkTheme = createMuiTheme({
},
});
+function PrivateRoute(props: any) {
+ const { children, ...rest } = props;
+ let auth = useAuth();
+ return
+ auth.user ? (
+ children
+ ) : (
+
+ )
+ }
+ />
+}
+
export default function MainWindow(props: any) {
return
@@ -29,30 +49,38 @@ export default function MainWindow(props: any) {
-
-
-
-
-
+
-
+
-
+
-
+
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
diff --git a/client/src/components/appbar/AppBar.tsx b/client/src/components/appbar/AppBar.tsx
index 28809f2..b39601f 100644
--- a/client/src/components/appbar/AppBar.tsx
+++ b/client/src/components/appbar/AppBar.tsx
@@ -1,10 +1,9 @@
import React from 'react';
-import { AppBar as MuiAppBar, Box, Tab as MuiTab, Tabs, IconButton, Typography } from '@material-ui/core';
-import CloseIcon from '@material-ui/icons/Close';
+import { AppBar as MuiAppBar, Box, Tab as MuiTab, Tabs, IconButton, Typography, Menu, MenuItem } from '@material-ui/core';
import SearchIcon from '@material-ui/icons/Search';
import LocalOfferIcon from '@material-ui/icons/LocalOffer';
import { Link, useHistory } from 'react-router-dom';
-import { WindowType } from '../windows/Windows';
+import { useAuth } from '../../lib/useAuth';
export enum AppBarTab {
Query = 0,
@@ -13,19 +12,58 @@ export enum AppBarTab {
export const appBarTabProps: Record = {
[AppBarTab.Query]: {
- label: Query,
+ label: Query,
path: "/query",
},
[AppBarTab.Tags]: {
- label: Tags,
+ label: Tags,
path: "/tags",
},
}
+export function UserMenu(props: {
+ position: null | number[],
+ open: boolean,
+ onLogout: () => void,
+ onClose: () => void,
+}) {
+ let auth = useAuth();
+ const pos = props.open && props.position ?
+ { left: props.position[0], top: props.position[1] }
+ : { left: 0, top: 0 }
+
+ return
+}
+
export default function AppBar(props: {
selectedTab: AppBarTab | null
}) {
- const history = useHistory();
+ let history = useHistory();
+ let auth = useAuth();
+
+ const [userMenuPos, setUserMenuPos] = React.useState(null);
+ const onOpenUserMenu = (e: any) => {
+ setUserMenuPos([e.clientX, e.clientY])
+ };
+ const onCloseUserMenu = () => {
+ setUserMenuPos(null);
+ };
return <>
@@ -35,18 +73,30 @@ export default function AppBar(props: {
- history.push(appBarTabProps[val].path)}
- variant="scrollable"
- scrollButtons="auto"
- >
- {Object.keys(appBarTabProps).map((tab: any, idx: number) => )}
-
+
+ {auth.user && history.push(appBarTabProps[val].path)}
+ variant="scrollable"
+ scrollButtons="auto"
+ >
+ {Object.keys(appBarTabProps).map((tab: any, idx: number) => )}
+ }
+
+ {auth.user && { onOpenUserMenu(e) }}
+ >{auth.user.icon}}
+
>
}
\ No newline at end of file
diff --git a/client/src/components/querybuilder/QBLeafElem.tsx b/client/src/components/querybuilder/QBLeafElem.tsx
index 6398b7e..dad3d03 100644
--- a/client/src/components/querybuilder/QBLeafElem.tsx
+++ b/client/src/components/querybuilder/QBLeafElem.tsx
@@ -110,56 +110,56 @@ export function QBLeafElem(props: IProps) {
: undefined;
- if (e.a == QueryLeafBy.ArtistName &&
- e.leafOp == QueryLeafOp.Equals &&
+ if (e.a === QueryLeafBy.ArtistName &&
+ e.leafOp === QueryLeafOp.Equals &&
typeof e.b == "string") {
return
- } else if (e.a == QueryLeafBy.ArtistName &&
- e.leafOp == QueryLeafOp.Like &&
+ } else if (e.a === QueryLeafBy.ArtistName &&
+ e.leafOp === QueryLeafOp.Like &&
typeof e.b == "string") {
return
- } else if (e.a == QueryLeafBy.AlbumName &&
- e.leafOp == QueryLeafOp.Equals &&
+ } else if (e.a === QueryLeafBy.AlbumName &&
+ e.leafOp === QueryLeafOp.Equals &&
typeof e.b == "string") {
return
- } else if (e.a == QueryLeafBy.AlbumName &&
- e.leafOp == QueryLeafOp.Like &&
+ } else if (e.a === QueryLeafBy.AlbumName &&
+ e.leafOp === QueryLeafOp.Like &&
typeof e.b == "string") {
return
- } if (e.a == QueryLeafBy.SongTitle &&
- e.leafOp == QueryLeafOp.Equals &&
+ } if (e.a === QueryLeafBy.SongTitle &&
+ e.leafOp === QueryLeafOp.Equals &&
typeof e.b == "string") {
return
- } else if (e.a == QueryLeafBy.SongTitle &&
- e.leafOp == QueryLeafOp.Like &&
+ } else if (e.a === QueryLeafBy.SongTitle &&
+ e.leafOp === QueryLeafOp.Like &&
typeof e.b == "string") {
return
- } else if (e.a == QueryLeafBy.TagInfo &&
- e.leafOp == QueryLeafOp.Equals &&
+ } else if (e.a === QueryLeafBy.TagInfo &&
+ e.leafOp === QueryLeafOp.Equals &&
isTagQueryInfo(e.b)) {
return
- }else if (e.leafOp == QueryLeafOp.Placeholder) {
+ }else if (e.leafOp === QueryLeafOp.Placeholder) {
return
});
- if (e.nodeOp == QueryNodeOp.And) {
+ if (e.nodeOp === QueryNodeOp.And) {
return {children}
- } else if (e.nodeOp == QueryNodeOp.Or) {
+ } else if (e.nodeOp === QueryNodeOp.Or) {
return {children}
}
- throw "Unsupported node element";
+ throw new Error("Unsupported node element");
}
\ No newline at end of file
diff --git a/client/src/components/querybuilder/QBSelectWithRequest.tsx b/client/src/components/querybuilder/QBSelectWithRequest.tsx
index 107e982..77d4574 100644
--- a/client/src/components/querybuilder/QBSelectWithRequest.tsx
+++ b/client/src/components/querybuilder/QBSelectWithRequest.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import CircularProgress from '@material-ui/core/CircularProgress';
@@ -26,26 +26,26 @@ export default function QBSelectWithRequest(props: IProps & any) {
const loading: boolean = !options || options.forInput !== input;
- const updateOptions = (forInput: string, options: any[]) => {
+ const updateOptions = useCallback((forInput: string, options: any[]) => {
if (forInput === input) {
setOptions({
forInput: forInput,
options: options,
});
}
- }
+ }, [setOptions, input]);
- const startRequest = (_input: string) => {
+ const startRequest = useCallback((_input: string) => {
setInput(_input);
(async () => {
const newOptions = await getNewOptions(_input);
updateOptions(_input, newOptions);
})();
- };
+ }, [setInput, getNewOptions, updateOptions]);
useEffect(() => {
startRequest(input);
- }, [input]);
+ }, [input, startRequest]);
const onInputChange = (e: any, val: any, reason: any) => {
if (reason === 'reset') {
diff --git a/client/src/components/tables/ResultsTable.tsx b/client/src/components/tables/ResultsTable.tsx
index 5fee3d1..e79fb37 100644
--- a/client/src/components/tables/ResultsTable.tsx
+++ b/client/src/components/tables/ResultsTable.tsx
@@ -46,14 +46,12 @@ export default function SongTable(props: {
{props.songs.map((song: any) => {
const title = props.songGetters.getTitle(song);
- // TODO / FIXME: display artists and albums separately!
+ // TODO: display artists and albums separately!
const artistNames = props.songGetters.getArtistNames(song);
const artist = stringifyList(artistNames);
const mainArtistId = props.songGetters.getArtistIds(song)[0];
- const mainArtistName = artistNames[0];
const albumNames = props.songGetters.getAlbumNames(song);
const album = stringifyList(albumNames);
- const mainAlbumName = albumNames[0];
const mainAlbumId = props.songGetters.getAlbumIds(song)[0];
const songId = props.songGetters.getId(song);
const tagIds = props.songGetters.getTagIds(song);
diff --git a/client/src/components/windows/Windows.tsx b/client/src/components/windows/Windows.tsx
index edd6768..9188b2f 100644
--- a/client/src/components/windows/Windows.tsx
+++ b/client/src/components/windows/Windows.tsx
@@ -12,6 +12,8 @@ import AlbumWindow, { AlbumWindowReducer } from './album/AlbumWindow';
import TagWindow, { TagWindowReducer } from './tag/TagWindow';
import { songGetters } from '../../lib/songGetters';
import ManageTagsWindow, { ManageTagsWindowReducer } from './manage_tags/ManageTagsWindow';
+import { RegisterWindowReducer } from './register/RegisterWindow';
+import { LoginWindowReducer } from './login/LoginWindow';
export enum WindowType {
Query = "Query",
@@ -20,6 +22,8 @@ export enum WindowType {
Tag = "Tag",
Song = "Song",
ManageTags = "ManageTags",
+ Login = "Login",
+ Register = "Register",
}
export interface WindowState { }
@@ -31,6 +35,8 @@ export const newWindowReducer = {
[WindowType.Song]: SongWindowReducer,
[WindowType.Tag]: TagWindowReducer,
[WindowType.ManageTags]: ManageTagsWindowReducer,
+ [WindowType.Login]: LoginWindowReducer,
+ [WindowType.Register]: RegisterWindowReducer,
}
export const newWindowState = {
@@ -81,5 +87,11 @@ export const newWindowState = {
alert: null,
pendingChanges: [],
}
- }
+ },
+ [WindowType.Login]: () => {
+ return {}
+ },
+ [WindowType.Register]: () => {
+ return {}
+ },
}
\ No newline at end of file
diff --git a/client/src/components/windows/album/AlbumWindow.tsx b/client/src/components/windows/album/AlbumWindow.tsx
index 07daa38..7d14329 100644
--- a/client/src/components/windows/album/AlbumWindow.tsx
+++ b/client/src/components/windows/album/AlbumWindow.tsx
@@ -12,7 +12,6 @@ import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import { queryAlbums, querySongs } from '../../../lib/backend/queries';
import { songGetters } from '../../../lib/songGetters';
import { useParams } from 'react-router';
-var _ = require('lodash');
export type AlbumMetadata = serverApi.AlbumDetails;
export type AlbumMetadataChanges = serverApi.ModifyAlbumRequest;
@@ -76,40 +75,40 @@ export function AlbumWindowControlled(props: {
state: AlbumWindowState,
dispatch: (action: any) => void,
}) {
- let metadata = props.state.metadata;
- let pendingChanges = props.state.pendingChanges;
+ let { id: albumId, metadata, pendingChanges, songsOnAlbum } = props.state;
+ let { dispatch } = props;
// Effect to get the album's metadata.
useEffect(() => {
- getAlbumMetadata(props.state.id)
+ getAlbumMetadata(albumId)
.then((m: AlbumMetadata) => {
- props.dispatch({
+ dispatch({
type: AlbumWindowStateActions.SetMetadata,
value: m
});
})
- }, [metadata?.name]);
+ }, [albumId, dispatch]);
// Effect to get the album's songs.
useEffect(() => {
- if (props.state.songsOnAlbum) { return; }
+ if (songsOnAlbum) { return; }
(async () => {
const songs = await querySongs({
query: {
a: QueryLeafBy.AlbumId,
- b: props.state.id,
+ b: albumId,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
limit: -1,
});
- props.dispatch({
+ dispatch({
type: AlbumWindowStateActions.SetSongs,
value: songs,
});
})();
- }, [props.state.songsOnAlbum]);
+ }, [songsOnAlbum, albumId, dispatch]);
const [editingName, setEditingName] = useState(null);
const name = {
const store = whichStore(link);
return store &&
void,
}) {
- let metadata = props.state.metadata;
- let pendingChanges = props.state.pendingChanges;
+ let { metadata, id: artistId, pendingChanges, songsByArtist } = props.state;
+ let { dispatch } = props;
// Effect to get the artist's metadata.
useEffect(() => {
- getArtistMetadata(props.state.id)
+ getArtistMetadata(artistId)
.then((m: ArtistMetadata) => {
- props.dispatch({
+ dispatch({
type: ArtistWindowStateActions.SetMetadata,
value: m
});
})
- }, [metadata?.name]);
+ }, [artistId, dispatch]);
// Effect to get the artist's songs.
useEffect(() => {
- if (props.state.songsByArtist) { return; }
+ if (songsByArtist) { return; }
(async () => {
const songs = await querySongs({
query: {
a: QueryLeafBy.ArtistId,
- b: props.state.id,
+ b: artistId,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
limit: -1,
});
- props.dispatch({
+ dispatch({
type: ArtistWindowStateActions.SetSongs,
value: songs,
});
})();
- }, [props.state.songsByArtist]);
+ }, [songsByArtist, dispatch, artistId]);
const [editingName, setEditingName] = useState(null);
const name = {
const store = whichStore(link);
return store &&
+}
+
+export function LoginWindowControlled(props: {
+ state: LoginWindowState,
+ dispatch: (action: any) => void,
+}) {
+ let history: any = useHistory();
+ let location: any = useLocation();
+ let auth: Auth = useAuth();
+ let { from } = location.state || { from: { pathname: "/" } };
+
+ const onSubmit = (event: any) => {
+ event.preventDefault();
+ auth.signin(props.state.email, props.state.password)
+ .then(() => {
+ history.replace(from);
+ }).catch((e: any) => {
+ props.dispatch({
+ type: LoginWindowStateActions.SetStatus,
+ value: LoginStatus.Unsuccessful,
+ })
+ })
+ }
+
+ return
+
+
+
+ Sign in
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/client/src/components/windows/manage_tags/ManageTagMenu.tsx b/client/src/components/windows/manage_tags/ManageTagMenu.tsx
index 376f340..38284ac 100644
--- a/client/src/components/windows/manage_tags/ManageTagMenu.tsx
+++ b/client/src/components/windows/manage_tags/ManageTagMenu.tsx
@@ -1,5 +1,5 @@
-import React, { useState } from 'react';
-import { Menu, MenuItem, TextField, Input } from '@material-ui/core';
+import React from 'react';
+import { Menu, MenuItem } from '@material-ui/core';
import NestedMenuItem from "material-ui-nested-menu-item";
import MenuEditText from '../../common/MenuEditText';
diff --git a/client/src/components/windows/manage_tags/ManageTagsWindow.tsx b/client/src/components/windows/manage_tags/ManageTagsWindow.tsx
index 285dfdd..9e4dd7e 100644
--- a/client/src/components/windows/manage_tags/ManageTagsWindow.tsx
+++ b/client/src/components/windows/manage_tags/ManageTagsWindow.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState, ReactFragment, useReducer } from 'react';
-import { WindowState, newWindowReducer, WindowType } from '../Windows';
+import { WindowState } from '../Windows';
import { Box, Typography, Chip, IconButton, useTheme, Button } from '@material-ui/core';
import LoyaltyIcon from '@material-ui/icons/Loyalty';
import ArrowRightIcon from '@material-ui/icons/ArrowRight';
@@ -9,8 +9,6 @@ import ControlTagChanges, { TagChange, TagChangeType, submitTagChanges } from '.
import { queryTags } from '../../../lib/backend/queries';
import NewTagMenu from './NewTagMenu';
import { v4 as genUuid } from 'uuid';
-import LocalOfferIcon from '@material-ui/icons/LocalOffer';
-import { songGetters } from '../../../lib/songGetters';
import Alert from '@material-ui/lab/Alert';
import { useHistory } from 'react-router';
var _ = require('lodash');
@@ -355,6 +353,8 @@ export function ManageTagsWindowControlled(props: {
dispatch: (action: any) => void,
}) {
const [newTagMenuPos, setNewTagMenuPos] = React.useState(null);
+ let { fetchedTags } = props.state;
+ let { dispatch } = props;
const onOpenNewTagMenu = (e: any) => {
setNewTagMenuPos([e.clientX, e.clientY])
@@ -364,19 +364,19 @@ export function ManageTagsWindowControlled(props: {
};
useEffect(() => {
- if (props.state.fetchedTags !== null) {
+ if (fetchedTags !== null) {
return;
}
(async () => {
const allTags = await getAllTags();
// We have the tags in list form. Now, we want to organize
// them hierarchically by giving each tag a "children" prop.
- props.dispatch({
+ dispatch({
type: ManageTagsWindowActions.SetFetchedTags,
value: allTags,
});
})();
- }, [props.state.fetchedTags]);
+ }, [fetchedTags, dispatch]);
const tagsWithChanges = annotateTagsWithChanges(props.state.fetchedTags || {}, props.state.pendingChanges || [])
const changedTags = organiseTags(
diff --git a/client/src/components/windows/manage_tags/NewTagMenu.tsx b/client/src/components/windows/manage_tags/NewTagMenu.tsx
index 3d16315..988ed93 100644
--- a/client/src/components/windows/manage_tags/NewTagMenu.tsx
+++ b/client/src/components/windows/manage_tags/NewTagMenu.tsx
@@ -1,5 +1,5 @@
-import React, { useState } from 'react';
-import { Menu, MenuItem, TextField, Input } from '@material-ui/core';
+import React from 'react';
+import { Menu } from '@material-ui/core';
import NestedMenuItem from "material-ui-nested-menu-item";
import MenuEditText from '../../common/MenuEditText';
diff --git a/client/src/components/windows/manage_tags/TagChange.tsx b/client/src/components/windows/manage_tags/TagChange.tsx
index cce1c06..b2e9c35 100644
--- a/client/src/components/windows/manage_tags/TagChange.tsx
+++ b/client/src/components/windows/manage_tags/TagChange.tsx
@@ -1,7 +1,5 @@
-import React, { useState, useEffect } from 'react';
+import React from 'react';
import { Typography, Chip, CircularProgress, Box, Paper } from '@material-ui/core';
-import { queryTags } from '../../../lib/backend/queries';
-import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import DiscardChangesButton from '../../common/DiscardChangesButton';
import SubmitChangesButton from '../../common/SubmitChangesButton';
import { createTag, modifyTag, deleteTag, mergeTag } from '../../../lib/backend/tags';
@@ -31,7 +29,7 @@ export async function submitTagChanges(changes: TagChange[]) {
var id_lookup: Record = {}
const getId = (id_string: string) => {
- return (Number(id_string) === NaN) ?
+ return (isNaN(Number(id_string))) ?
id_lookup[id_string] : Number(id_string);
}
diff --git a/client/src/components/windows/query/QueryWindow.tsx b/client/src/components/windows/query/QueryWindow.tsx
index 9561883..85afec5 100644
--- a/client/src/components/windows/query/QueryWindow.tsx
+++ b/client/src/components/windows/query/QueryWindow.tsx
@@ -1,24 +1,13 @@
-import React, { useEffect, useReducer } from 'react';
-import { createMuiTheme, Box, LinearProgress } from '@material-ui/core';
-import { QueryElem, toApiQuery, QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
+import React, { useEffect, useReducer, useCallback } from 'react';
+import { Box, LinearProgress } from '@material-ui/core';
+import { QueryElem, QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import QueryBuilder from '../../querybuilder/QueryBuilder';
-import * as serverApi from '../../../api';
import SongTable from '../../tables/ResultsTable';
import { songGetters } from '../../../lib/songGetters';
import { queryArtists, querySongs, queryAlbums, queryTags } from '../../../lib/backend/queries';
-import { grey } from '@material-ui/core/colors';
import { WindowState } from '../Windows';
var _ = require('lodash');
-const darkTheme = createMuiTheme({
- palette: {
- type: 'dark',
- primary: {
- main: grey[100],
- }
- },
-});
-
export interface ResultsForQuery {
for: QueryElem,
results: any[],
@@ -112,23 +101,23 @@ export function QueryWindowControlled(props: {
state: QueryWindowState,
dispatch: (action: any) => void,
}) {
- let query = props.state.query;
- let editing = props.state.editingQuery;
- let resultsFor = props.state.resultsForQuery;
+ let { query, editingQuery: editing, resultsForQuery: resultsFor } = props.state;
+ let { dispatch } = props;
+
let setQuery = (q: QueryElem | null) => {
props.dispatch({ type: QueryWindowStateActions.SetQuery, value: q });
}
let setEditingQuery = (e: boolean) => {
props.dispatch({ type: QueryWindowStateActions.SetEditingQuery, value: e });
}
- let setResultsForQuery = (r: ResultsForQuery | null) => {
- props.dispatch({ type: QueryWindowStateActions.SetResultsForQuery, value: r });
- }
+ let setResultsForQuery = useCallback((r: ResultsForQuery | null) => {
+ dispatch({ type: QueryWindowStateActions.SetResultsForQuery, value: r });
+ }, [ dispatch ]);
const loading = query && (!resultsFor || !_.isEqual(resultsFor.for, query));
- const showResults = (query && resultsFor && query == resultsFor.for) ? resultsFor.results : [];
+ const showResults = (query && resultsFor && query === resultsFor.for) ? resultsFor.results : [];
- const doQuery = async (_query: QueryElem) => {
+ const doQuery = useCallback(async (_query: QueryElem) => {
const songs = await querySongs({
query: _query,
offset: 0,
@@ -141,7 +130,7 @@ export function QueryWindowControlled(props: {
results: songs,
})
}
- }
+ }, [query, setResultsForQuery]);
useEffect(() => {
if (query) {
@@ -149,7 +138,7 @@ export function QueryWindowControlled(props: {
} else {
setResultsForQuery(null);
}
- }, [query]);
+ }, [query, doQuery, setResultsForQuery]);
return
+}
+
+export function RegisterWindowControlled(props: {
+ state: RegisterWindowState,
+ dispatch: (action: any) => void,
+}) {
+ let history: any = useHistory();
+ let auth: Auth = useAuth();
+
+ const onSubmit = (event: any) => {
+ event.preventDefault();
+ auth.signup(props.state.email, props.state.password)
+ .then(() => {
+ console.log("succes!")
+ props.dispatch({
+ type: RegisterWindowStateActions.SetStatus,
+ value: RegistrationStatus.Successful,
+ })
+ }).catch((e: any) => {
+ console.log("Fail!")
+ props.dispatch({
+ type: RegisterWindowStateActions.SetStatus,
+ value: RegistrationStatus.Unsuccessful,
+ })
+ })
+ }
+
+ return
+
+
+
+ Sign up
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/client/src/components/windows/song/SongWindow.tsx b/client/src/components/windows/song/SongWindow.tsx
index 6016366..c0f9e93 100644
--- a/client/src/components/windows/song/SongWindow.tsx
+++ b/client/src/components/windows/song/SongWindow.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState, useReducer } from 'react';
-import { Box, Typography, IconButton, Button, CircularProgress } from '@material-ui/core';
+import { Box, Typography, IconButton, CircularProgress } from '@material-ui/core';
import AudiotrackIcon from '@material-ui/icons/Audiotrack';
import PersonIcon from '@material-ui/icons/Person';
import AlbumIcon from '@material-ui/icons/Album';
@@ -13,7 +13,6 @@ import SubmitChangesButton from '../../common/SubmitChangesButton';
import { saveSongChanges } from '../../../lib/saveChanges';
import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import { querySongs } from '../../../lib/backend/queries';
-import { songGetters } from '../../../lib/songGetters';
import { useParams } from 'react-router';
export type SongMetadata = serverApi.SongDetails;
@@ -44,11 +43,6 @@ export function SongWindowReducer(state: SongWindowState, action: any) {
}
}
-export interface IProps {
- state: SongWindowState,
- dispatch: (action: any) => void,
-}
-
export async function getSongMetadata(id: number) {
return (await querySongs({
query: {
@@ -76,18 +70,18 @@ export function SongWindowControlled(props: {
state: SongWindowState,
dispatch: (action: any) => void,
}) {
- let metadata = props.state.metadata;
- let pendingChanges = props.state.pendingChanges;
+ let { pendingChanges, metadata, id: songId } = props.state;
+ let { dispatch } = props;
useEffect(() => {
- getSongMetadata(props.state.id)
+ getSongMetadata(songId)
.then((m: SongMetadata) => {
- props.dispatch({
+ dispatch({
type: SongWindowStateActions.SetMetadata,
value: m
});
})
- }, [metadata?.title]);
+ }, [songId, dispatch]);
const [editingTitle, setEditingTitle] = useState(null);
const title = {
const store = whichStore(link);
return store &&
{
- getTagMetadata(props.state.id)
+ getTagMetadata(tagId)
.then((m: TagMetadata) => {
- props.dispatch({
+ dispatch({
type: TagWindowStateActions.SetMetadata,
value: m
});
})
- }, [metadata?.name]);
+ }, [tagId, dispatch]);
// Effect to get the tag's songs.
useEffect(() => {
- if (props.state.songsWithTag) { return; }
+ if (songsWithTag) { return; }
(async () => {
const songs = await querySongs({
query: {
a: QueryLeafBy.TagId,
- b: props.state.id,
+ b: tagId,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
limit: -1,
});
- props.dispatch({
+ dispatch({
type: TagWindowStateActions.SetSongs,
value: songs,
- });
+ });
})();
- }, [props.state.songsWithTag]);
+ }, [songsWithTag, tagId, dispatch]);
const [editingName, setEditingName] = useState(null);
const name =
const fullName =
{metadata?.fullName.map((n: string, i: number) => {
- if (metadata?.fullName && i == metadata?.fullName.length - 1) {
+ if (metadata?.fullName && i === metadata?.fullName.length - 1) {
return name;
} else if (i >= (metadata?.fullName.length || 0) - 1) {
return undefined;
@@ -160,7 +161,7 @@ export function TagWindowControlled(props: {
const storeLinks = metadata?.storeLinks && metadata?.storeLinks.map((link: string) => {
const store = whichStore(link);
return store &&
{
- if (isLeafElem(op) && op.leafOp == QueryLeafOp.Placeholder) {
+ if (isLeafElem(op) && op.leafOp === QueryLeafOp.Placeholder) {
return;
}
const newOp = removePlaceholders(op);
@@ -136,14 +136,14 @@ export function removePlaceholders(q: QueryElem | null): QueryElem | null {
}
})
- if (newOperands.length == 0) {
+ if (newOperands.length === 0) {
return null;
}
- if (newOperands.length == 1) {
+ if (newOperands.length === 1) {
return newOperands[0];
}
return { operands: newOperands, nodeOp: q.nodeOp };
- } else if (q && isLeafElem(q) && q.leafOp == QueryLeafOp.Placeholder) {
+ } else if (q && isLeafElem(q) && q.leafOp === QueryLeafOp.Placeholder) {
return null;
}
diff --git a/client/src/lib/useAuth.tsx b/client/src/lib/useAuth.tsx
new file mode 100644
index 0000000..d6b4995
--- /dev/null
+++ b/client/src/lib/useAuth.tsx
@@ -0,0 +1,102 @@
+// Note: Based on https://usehooks.com/useAuth/
+
+
+import React, { useState, useContext, createContext, ReactFragment } from "react";
+import PersonIcon from '@material-ui/icons/Person';
+import * as serverApi from '../api';
+
+export interface AuthUser {
+ id: number,
+ email: string,
+ icon: ReactFragment,
+}
+
+export interface Auth {
+ user: AuthUser | null,
+ signout: () => void,
+ signin: (email: string, password: string) => Promise,
+ signup: (email: string, password: string) => Promise,
+};
+
+const authContext = createContext({
+ user: null,
+ signout: () => { },
+ signin: (email: string, password: string) => {
+ throw new Error("Auth object not initialized.");
+ },
+ signup: (email: string, password: string) => {
+ throw new Error("Auth object not initialized.");
+ },
+});
+
+export function ProvideAuth(props: { children: any }) {
+ const auth = useProvideAuth();
+ return {props.children};
+}
+
+export const useAuth = () => {
+ return useContext(authContext);
+};
+
+function useProvideAuth() {
+ const [user, setUser] = useState(null);
+
+ // TODO: password maybe shouldn't be encoded into the URL.
+ const signin = (email: string, password: string) => {
+ return (async () => {
+ const urlBase = (process.env.REACT_APP_BACKEND || "") + serverApi.LoginEndpoint;
+ const url = `${urlBase}?username=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`;
+
+ const response = await fetch(url, { method: "POST" });
+ const json = await response.json();
+ if (!("userId" in json)) {
+ throw new Error("No UserID received from login.");
+ }
+
+ const user = {
+ id: json.userId,
+ email: email,
+ icon: ,
+ }
+ setUser(user);
+ return user;
+ })();
+ };
+
+ const signup = (email: string, password: string) => {
+ return (async () => {
+ const requestOpts = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ email: email,
+ password: password,
+ })
+ };
+
+ const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.RegisterUserEndpoint, requestOpts)
+ if (!response.ok) {
+ throw new Error("Failed to register user.")
+ }
+ })();
+ };
+
+ const signout = () => {
+ return (async () => {
+ const url = (process.env.REACT_APP_BACKEND || "") + serverApi.LogoutEndpoint;
+ const response = await fetch(url, { method: "POST" });
+ if (!response.ok) {
+ throw new Error("Failed to log out.");
+ }
+ setUser(null);
+ })();
+ };
+
+ // Return the user object and auth methods
+ return {
+ user,
+ signin,
+ signup,
+ signout,
+ };
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 2eccf81..f7b29a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,37 +4,6 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
- "@types/node": {
- "version": "14.0.19",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.19.tgz",
- "integrity": "sha512-yf3BP/NIXF37BjrK5klu//asUWitOEoUP5xE1mhSUjazotwJ/eJDgEmMQNlOeWOVv72j24QQ+3bqXHE++CFGag=="
- },
- "abbrev": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
- },
- "accepts": {
- "version": "1.3.7",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
- "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
- "requires": {
- "mime-types": "~2.1.24",
- "negotiator": "0.6.2"
- }
- },
- "ajv": {
- "version": "6.12.3",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz",
- "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
- "optional": true,
- "requires": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- }
- },
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
@@ -44,122 +13,19 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
- "any-promise": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
- },
- "aproba": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
- "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
- },
- "are-we-there-yet": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
- "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
- "requires": {
- "delegates": "^1.0.0",
- "readable-stream": "^2.0.6"
- }
- },
- "array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
- },
- "asn1": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
- "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
- "optional": true,
- "requires": {
- "safer-buffer": "~2.1.0"
- }
- },
- "assert-plus": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
- "optional": true
- },
- "asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
- "optional": true
- },
- "aws-sign2": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
- "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
- "optional": true
- },
- "aws4": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz",
- "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==",
- "optional": true
- },
- "balanced-match": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
- },
- "bcrypt-pbkdf": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
- "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
- "optional": true,
- "requires": {
- "tweetnacl": "^0.14.3"
- }
- },
- "block-stream": {
- "version": "0.0.9",
- "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
- "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
- "optional": true,
- "requires": {
- "inherits": "~2.0.0"
- }
- },
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "bytes": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
- "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
- },
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
- "dev": true
- },
- "caseless": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
- "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
- "optional": true
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -170,23 +36,16 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
- "chownr": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
- },
"cliui": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
"integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
- "dev": true,
"requires": {
"string-width": "^2.1.1",
"strip-ansi": "^4.0.0",
@@ -196,14 +55,12 @@
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
- "dev": true
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
@@ -219,7 +76,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
"requires": {
"color-name": "1.1.3"
}
@@ -227,26 +83,12 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
- "dev": true
- },
- "combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "optional": true,
- "requires": {
- "delayed-stream": "~1.0.0"
- }
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"concurrently": {
"version": "4.1.2",
- "dev": true,
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.2.tgz",
+ "integrity": "sha512-Kim9SFrNr2jd8/0yNYqDTFALzUX1tvimmwFWxmp/D4mRI+kbqIIwE2RkBDrxS2ic25O1UgQMI5AtBqdtX3ynYg==",
"requires": {
"chalk": "^2.4.2",
"date-fns": "^1.30.1",
@@ -259,24 +101,6 @@
"yargs": "^12.0.5"
}
},
- "console-control-strings": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
- "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
- },
- "content-disposition": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
- "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
- "requires": {
- "safe-buffer": "5.1.2"
- }
- },
- "content-type": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
- },
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
@@ -287,16 +111,10 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
- "core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
- },
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
- "dev": true,
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
@@ -305,20 +123,10 @@
"which": "^1.2.9"
}
},
- "dashdash": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
- "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
- "optional": true,
- "requires": {
- "assert-plus": "^1.0.0"
- }
- },
"date-fns": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
- "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
- "dev": true
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw=="
},
"debug": {
"version": "2.6.9",
@@ -331,70 +139,12 @@
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
- "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
- "dev": true
- },
- "deep-extend": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
- "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
- },
- "delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
- "optional": true
- },
- "delegates": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
- "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
- },
- "depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
- },
- "destroy": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
- "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
- },
- "detect-libc": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
- "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
- },
- "dottie": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz",
- "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg=="
- },
- "ecc-jsbn": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
- "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
- "optional": true,
- "requires": {
- "jsbn": "~0.1.0",
- "safer-buffer": "^2.1.0"
- }
- },
- "ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
- },
- "encodeurl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
- "dev": true,
"requires": {
"once": "^1.4.0"
}
@@ -403,32 +153,19 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
- "dev": true,
"requires": {
"is-arrayish": "^0.2.1"
}
},
- "escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
- },
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
- "dev": true
- },
- "etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"execa": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
- "dev": true,
"requires": {
"cross-spawn": "^6.0.0",
"get-stream": "^4.0.0",
@@ -439,349 +176,73 @@
"strip-eof": "^1.0.0"
}
},
- "express": {
- "version": "4.17.1",
+ "express-session": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz",
+ "integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==",
"requires": {
- "accepts": "~1.3.7",
- "array-flatten": "1.1.1",
- "body-parser": "1.19.0",
- "content-disposition": "0.5.3",
- "content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
- "depd": "~1.1.2",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "~1.1.2",
- "fresh": "0.5.2",
- "merge-descriptors": "1.0.1",
- "methods": "~1.1.2",
- "on-finished": "~2.3.0",
+ "depd": "~2.0.0",
+ "on-headers": "~1.0.2",
"parseurl": "~1.3.3",
- "path-to-regexp": "0.1.7",
- "proxy-addr": "~2.0.5",
- "qs": "6.7.0",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.1.2",
- "send": "0.17.1",
- "serve-static": "1.14.1",
- "setprototypeof": "1.1.1",
- "statuses": "~1.5.0",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
+ "safe-buffer": "5.2.0",
+ "uid-safe": "~2.1.5"
},
"dependencies": {
- "body-parser": {
- "version": "1.19.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
- "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
- "requires": {
- "bytes": "3.1.0",
- "content-type": "~1.0.4",
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "http-errors": "1.7.2",
- "iconv-lite": "0.4.24",
- "on-finished": "~2.3.0",
- "qs": "6.7.0",
- "raw-body": "2.4.0",
- "type-is": "~1.6.17"
- }
+ "depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+ },
+ "safe-buffer": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+ "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
}
}
},
- "extend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "optional": true
- },
- "extsprintf": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
- "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
- "optional": true
- },
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "optional": true
- },
- "fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "optional": true
- },
- "finalhandler": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
- "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
- "requires": {
- "debug": "2.6.9",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "on-finished": "~2.3.0",
- "parseurl": "~1.3.3",
- "statuses": "~1.5.0",
- "unpipe": "~1.0.0"
- }
- },
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
- "dev": true,
"requires": {
"locate-path": "^3.0.0"
}
},
- "forever-agent": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
- "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
- "optional": true
- },
- "form-data": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
- "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
- "optional": true,
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.6",
- "mime-types": "^2.1.12"
- }
- },
- "forwarded": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
- "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
- },
- "fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
- },
- "fs-minipass": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
- "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
- "requires": {
- "minipass": "^2.6.0"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
- },
- "fstream": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
- "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
- "optional": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "inherits": "~2.0.0",
- "mkdirp": ">=0.5 0",
- "rimraf": "2"
- }
- },
- "gauge": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
- "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
- "requires": {
- "aproba": "^1.0.3",
- "console-control-strings": "^1.0.0",
- "has-unicode": "^2.0.0",
- "object-assign": "^4.1.0",
- "signal-exit": "^3.0.0",
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wide-align": "^1.1.0"
- },
- "dependencies": {
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- }
- }
- },
"get-caller-file": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
- "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
- "dev": true
+ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w=="
},
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
- "dev": true,
"requires": {
"pump": "^3.0.0"
}
},
- "getpass": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
- "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
- "optional": true,
- "requires": {
- "assert-plus": "^1.0.0"
- }
- },
- "glob": {
- "version": "7.1.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
- "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "graceful-fs": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
- "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
- "optional": true
- },
- "har-schema": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
- "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
- "optional": true
- },
- "har-validator": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
- "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
- "optional": true,
- "requires": {
- "ajv": "^6.5.5",
- "har-schema": "^2.0.0"
- }
- },
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
- "dev": true
- },
- "has-unicode": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
- "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
- "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
- "dev": true
- },
- "http-errors": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
- "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
- "requires": {
- "depd": "~1.1.2",
- "inherits": "2.0.3",
- "setprototypeof": "1.1.1",
- "statuses": ">= 1.5.0 < 2",
- "toidentifier": "1.0.0"
- }
- },
- "http-signature": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
- "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
- "optional": true,
- "requires": {
- "assert-plus": "^1.0.0",
- "jsprim": "^1.2.2",
- "sshpk": "^1.7.0"
- }
- },
- "iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "requires": {
- "safer-buffer": ">= 2.1.2 < 3"
- }
- },
- "ignore-walk": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
- "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
- "requires": {
- "minimatch": "^3.0.4"
- }
- },
- "inflection": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz",
- "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY="
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
- },
- "ini": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
- "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+ "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg=="
},
"invert-kv": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
- "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
- "dev": true
- },
- "ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA=="
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
- "dev": true
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
@@ -791,78 +252,22 @@
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
- "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
- "dev": true
- },
- "is-typedarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
- "optional": true
- },
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
- "isstream": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
- "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
- "optional": true
- },
- "jsbn": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
- "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
- "optional": true
- },
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
- "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
- "dev": true
- },
- "json-schema": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
- "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
- "optional": true
- },
- "json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "optional": true
- },
- "json-stringify-safe": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
- "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
- "optional": true
- },
- "jsprim": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
- "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
- "optional": true,
- "requires": {
- "assert-plus": "1.0.0",
- "extsprintf": "1.3.0",
- "json-schema": "0.2.3",
- "verror": "1.10.0"
- }
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
},
"lcid": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
"integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
- "dev": true,
"requires": {
"invert-kv": "^2.0.0"
}
@@ -871,7 +276,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
- "dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
@@ -886,242 +290,39 @@
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
"integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
- "dev": true,
"requires": {
"p-defer": "^1.0.0"
}
},
- "media-typer": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
- },
"mem": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
"integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
- "dev": true,
"requires": {
"map-age-cleaner": "^0.1.1",
"mimic-fn": "^2.0.0",
"p-is-promise": "^2.0.0"
}
},
- "merge-descriptors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
- "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
- },
- "methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
- },
- "mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
- },
- "mime-db": {
- "version": "1.44.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
- "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg=="
- },
- "mime-types": {
- "version": "2.1.27",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
- "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
- "requires": {
- "mime-db": "1.44.0"
- }
- },
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
- "dev": true
- },
- "minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "minimist": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
- },
- "minipass": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
- "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
- "requires": {
- "safe-buffer": "^5.1.2",
- "yallist": "^3.0.0"
- }
- },
- "minizlib": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
- "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
- "requires": {
- "minipass": "^2.9.0"
- }
- },
- "mkdirp": {
- "version": "0.5.5",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
- "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
- "requires": {
- "minimist": "^1.2.5"
- }
- },
- "moment": {
- "version": "2.27.0",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
- "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
- },
- "moment-timezone": {
- "version": "0.5.31",
- "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz",
- "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==",
- "requires": {
- "moment": ">= 2.9.0"
- }
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
- "needle": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz",
- "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==",
- "requires": {
- "debug": "^3.2.6",
- "iconv-lite": "^0.4.4",
- "sax": "^1.2.4"
- },
- "dependencies": {
- "debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- }
- }
- },
- "negotiator": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
- "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
- },
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
- "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
- "dev": true
- },
- "node-addon-api": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz",
- "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA=="
- },
- "node-gyp": {
- "version": "3.8.0",
- "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
- "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
- "optional": true,
- "requires": {
- "fstream": "^1.0.0",
- "glob": "^7.0.3",
- "graceful-fs": "^4.1.2",
- "mkdirp": "^0.5.0",
- "nopt": "2 || 3",
- "npmlog": "0 || 1 || 2 || 3 || 4",
- "osenv": "0",
- "request": "^2.87.0",
- "rimraf": "2",
- "semver": "~5.3.0",
- "tar": "^2.0.0",
- "which": "1"
- },
- "dependencies": {
- "semver": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
- "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
- "optional": true
- }
- }
- },
- "node-pre-gyp": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
- "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==",
- "requires": {
- "detect-libc": "^1.0.2",
- "mkdirp": "^0.5.1",
- "needle": "^2.2.1",
- "nopt": "^4.0.1",
- "npm-packlist": "^1.1.6",
- "npmlog": "^4.0.2",
- "rc": "^1.2.7",
- "rimraf": "^2.6.1",
- "semver": "^5.3.0",
- "tar": "^4"
- },
- "dependencies": {
- "nopt": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
- "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
- "requires": {
- "abbrev": "1",
- "osenv": "^0.1.4"
- }
- },
- "tar": {
- "version": "4.4.13",
- "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
- "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
- "requires": {
- "chownr": "^1.1.1",
- "fs-minipass": "^1.2.5",
- "minipass": "^2.8.6",
- "minizlib": "^1.2.1",
- "mkdirp": "^0.5.0",
- "safe-buffer": "^5.1.2",
- "yallist": "^3.0.3"
- }
- }
- }
- },
- "nopt": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
- "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
- "optional": true,
- "requires": {
- "abbrev": "1"
- }
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
},
"normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
- "dev": true,
"requires": {
"hosted-git-info": "^2.1.4",
"resolve": "^1.10.0",
@@ -1129,72 +330,23 @@
"validate-npm-package-license": "^3.0.1"
}
},
- "npm-bundled": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
- "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
- "requires": {
- "npm-normalize-package-bin": "^1.0.1"
- }
- },
- "npm-normalize-package-bin": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
- "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
- },
- "npm-packlist": {
- "version": "1.4.8",
- "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
- "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
- "requires": {
- "ignore-walk": "^3.0.1",
- "npm-bundled": "^1.0.1",
- "npm-normalize-package-bin": "^1.0.1"
- }
- },
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
- "dev": true,
"requires": {
"path-key": "^2.0.0"
}
},
- "npmlog": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
- "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
- "requires": {
- "are-we-there-yet": "~1.1.2",
- "console-control-strings": "~1.1.0",
- "gauge": "~2.7.3",
- "set-blocking": "~2.0.0"
- }
- },
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
- "oauth-sign": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
- "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
- "optional": true
- },
- "object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
- },
- "on-finished": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
- "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
- "requires": {
- "ee-first": "1.1.1"
- }
+ "on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
},
"once": {
"version": "1.4.0",
@@ -1204,59 +356,35 @@
"wrappy": "1"
}
},
- "os-homedir": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
- "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
- },
"os-locale": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
"integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
- "dev": true,
"requires": {
"execa": "^1.0.0",
"lcid": "^2.0.0",
"mem": "^4.0.0"
}
},
- "os-tmpdir": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
- "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
- },
- "osenv": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
- "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
- "requires": {
- "os-homedir": "^1.0.0",
- "os-tmpdir": "^1.0.0"
- }
- },
"p-defer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
- "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
- "dev": true
+ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww="
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
- "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
- "dev": true
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"p-is-promise": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
- "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
- "dev": true
+ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg=="
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
"requires": {
"p-try": "^2.0.0"
}
@@ -1265,7 +393,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
- "dev": true,
"requires": {
"p-limit": "^2.0.0"
}
@@ -1273,14 +400,12 @@
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
- "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
- "dev": true
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
- "dev": true,
"requires": {
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1"
@@ -1294,342 +419,87 @@
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
- "dev": true
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
},
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
- "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
- "dev": true
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
- "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
- "dev": true
- },
- "path-to-regexp": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
- "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
- },
- "performance-now": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
- "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
- "optional": true
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
- "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
- "dev": true
- },
- "process-nextick-args": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
- },
- "proxy-addr": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
- "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
- "requires": {
- "forwarded": "~0.1.2",
- "ipaddr.js": "1.9.1"
- }
- },
- "psl": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
- "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
- "optional": true
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
- "dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
- "punycode": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "optional": true
- },
- "qs": {
- "version": "6.7.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
- "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
- },
- "range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
- },
- "raw-body": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
- "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
- "requires": {
- "bytes": "3.1.0",
- "http-errors": "1.7.2",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
- }
- },
- "rc": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
- "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
- "requires": {
- "deep-extend": "^0.6.0",
- "ini": "~1.3.0",
- "minimist": "^1.2.0",
- "strip-json-comments": "~2.0.1"
- }
+ "random-bytes": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+ "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
},
"read-pkg": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
"integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=",
- "dev": true,
"requires": {
"normalize-package-data": "^2.3.2",
"parse-json": "^4.0.0",
"pify": "^3.0.0"
}
},
- "readable-stream": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
- "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "request": {
- "version": "2.88.2",
- "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
- "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
- "optional": true,
- "requires": {
- "aws-sign2": "~0.7.0",
- "aws4": "^1.8.0",
- "caseless": "~0.12.0",
- "combined-stream": "~1.0.6",
- "extend": "~3.0.2",
- "forever-agent": "~0.6.1",
- "form-data": "~2.3.2",
- "har-validator": "~5.1.3",
- "http-signature": "~1.2.0",
- "is-typedarray": "~1.0.0",
- "isstream": "~0.1.2",
- "json-stringify-safe": "~5.0.1",
- "mime-types": "~2.1.19",
- "oauth-sign": "~0.9.0",
- "performance-now": "^2.1.0",
- "qs": "~6.5.2",
- "safe-buffer": "^5.1.2",
- "tough-cookie": "~2.5.0",
- "tunnel-agent": "^0.6.0",
- "uuid": "^3.3.2"
- },
- "dependencies": {
- "qs": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
- "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
- "optional": true
- },
- "uuid": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
- "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
- "optional": true
- }
- }
- },
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
- "dev": true
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-main-filename": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
- "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
- "dev": true
+ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
},
"resolve": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
"integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
- "dev": true,
"requires": {
"path-parse": "^1.0.6"
}
},
- "retry-as-promised": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz",
- "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==",
- "requires": {
- "any-promise": "^1.3.0"
- }
- },
- "rimraf": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
- "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
- "requires": {
- "glob": "^7.1.3"
- }
- },
"rxjs": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz",
"integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==",
- "dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
- },
- "safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
- },
- "sax": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
- },
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
- "send": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
- "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
- "requires": {
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "destroy": "~1.0.4",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "~1.7.2",
- "mime": "1.6.0",
- "ms": "2.1.1",
- "on-finished": "~2.3.0",
- "range-parser": "~1.2.1",
- "statuses": "~1.5.0"
- },
- "dependencies": {
- "ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
- }
- }
- },
- "sequelize": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.0.tgz",
- "integrity": "sha512-aVZUvT0w1ebewlApFuaUJE/fJ7aTfIpMnwNM/Zgr29QnY0fT1t0EjXxl48Fwmfq3BHJogLMhfMTJRXJQaiaFVQ==",
- "requires": {
- "debug": "^4.1.1",
- "dottie": "^2.0.0",
- "inflection": "1.12.0",
- "lodash": "^4.17.15",
- "moment": "^2.26.0",
- "moment-timezone": "^0.5.31",
- "retry-as-promised": "^3.2.0",
- "semver": "^7.3.2",
- "sequelize-pool": "^6.0.0",
- "toposort-class": "^1.0.1",
- "uuid": "^8.1.0",
- "validator": "^10.11.0",
- "wkx": "^0.5.0"
- },
- "dependencies": {
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "semver": {
- "version": "7.3.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
- "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
- }
- }
- },
- "sequelize-pool": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.0.0.tgz",
- "integrity": "sha512-D/VfOX2Z+6JTWqM73lhcqMXp1X4CeqRNVMlndvbOMtyjFAZ2kYzH7rGFGFrLO1r+RZQdc/h+3zQL4nd3cclNLg=="
- },
- "serve-static": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
- "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
- "requires": {
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.17.1"
- }
- },
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
- "setprototypeof": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
- "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
- },
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
- "dev": true,
"requires": {
"shebang-regex": "^1.0.0"
}
@@ -1637,8 +507,7 @@
"shebang-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
- "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
- "dev": true
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
"signal-exit": {
"version": "3.0.3",
@@ -1648,14 +517,12 @@
"spawn-command": {
"version": "0.0.2-1",
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
- "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=",
- "dev": true
+ "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A="
},
"spdx-correct": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
"integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
- "dev": true,
"requires": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
@@ -1664,14 +531,12 @@
"spdx-exceptions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
- "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
- "dev": true
+ "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A=="
},
"spdx-expression-parse": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
- "dev": true,
"requires": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
@@ -1680,40 +545,7 @@
"spdx-license-ids": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
- "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
- "dev": true
- },
- "sqlite3": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.0.tgz",
- "integrity": "sha512-rjvqHFUaSGnzxDy2AHCwhHy6Zp6MNJzCPGYju4kD8yi6bze4d1/zMTg6C7JI49b7/EM7jKMTvyfN/4ylBKdwfw==",
- "requires": {
- "node-addon-api": "2.0.0",
- "node-gyp": "3.x",
- "node-pre-gyp": "^0.11.0"
- }
- },
- "sshpk": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
- "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
- "optional": true,
- "requires": {
- "asn1": "~0.2.3",
- "assert-plus": "^1.0.0",
- "bcrypt-pbkdf": "^1.0.0",
- "dashdash": "^1.12.0",
- "ecc-jsbn": "~0.1.1",
- "getpass": "^0.1.1",
- "jsbn": "~0.1.0",
- "safer-buffer": "^2.0.2",
- "tweetnacl": "~0.14.0"
- }
- },
- "statuses": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+ "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q=="
},
"string-width": {
"version": "2.1.1",
@@ -1739,14 +571,6 @@
}
}
},
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
@@ -1758,19 +582,12 @@
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
- "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
- "dev": true
- },
- "strip-json-comments": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
- "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
"supports-color": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
"integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
- "dev": true,
"requires": {
"has-flag": "^2.0.0"
},
@@ -1778,138 +595,37 @@
"has-flag": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
- "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
- "dev": true
+ "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE="
}
}
},
- "tar": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
- "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
- "optional": true,
- "requires": {
- "block-stream": "*",
- "fstream": "^1.0.12",
- "inherits": "2"
- }
- },
- "toidentifier": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
- "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
- },
- "toposort-class": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz",
- "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg="
- },
- "tough-cookie": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
- "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
- "optional": true,
- "requires": {
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
- }
- },
"tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
- "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
- "dev": true
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="
},
"tslib": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
- "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
- "dev": true
- },
- "tunnel-agent": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
- "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
- "optional": true,
- "requires": {
- "safe-buffer": "^5.0.1"
- }
- },
- "tweetnacl": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
- "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
- "optional": true
+ "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
},
- "type-is": {
- "version": "1.6.18",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "uid-safe": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+ "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"requires": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
+ "random-bytes": "~1.0.0"
}
},
- "unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
- },
- "uri-js": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
- "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
- "optional": true,
- "requires": {
- "punycode": "^2.1.0"
- }
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
- },
- "utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
- },
- "uuid": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz",
- "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q=="
- },
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
- "dev": true,
"requires": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
}
},
- "validator": {
- "version": "10.11.0",
- "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz",
- "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw=="
- },
- "vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
- },
- "verror": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
- "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
- "optional": true,
- "requires": {
- "assert-plus": "^1.0.0",
- "core-util-is": "1.0.2",
- "extsprintf": "^1.2.0"
- }
- },
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -1921,30 +637,12 @@
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
- "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
- "dev": true
- },
- "wide-align": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
- "requires": {
- "string-width": "^1.0.2 || 2"
- }
- },
- "wkx": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",
- "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==",
- "requires": {
- "@types/node": "*"
- }
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
- "dev": true,
"requires": {
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1"
@@ -1954,7 +652,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "dev": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -1963,7 +660,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "dev": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -1980,19 +676,12 @@
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
- "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
- "dev": true
- },
- "yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
},
"yargs": {
"version": "12.0.5",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
"integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
- "dev": true,
"requires": {
"cliui": "^4.0.0",
"decamelize": "^1.2.0",
@@ -2012,7 +701,6 @@
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
"integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
- "dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
diff --git a/package.json b/package.json
index f27a93e..a24106b 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"start": "npm run-script dev"
},
"dependencies": {
- "concurrently": "^4.0.1"
+ "concurrently": "^4.0.1",
+ "express-session": "^1.17.1"
}
}
diff --git a/scripts/gpm_retrieve/gpm_retrieve.py b/scripts/gpm_retrieve/gpm_retrieve.py
index c20a02d..87fc9f3 100755
--- a/scripts/gpm_retrieve/gpm_retrieve.py
+++ b/scripts/gpm_retrieve/gpm_retrieve.py
@@ -1,36 +1,49 @@
#!/usr/bin/env python3
-from gmusicapi import Mobileclient
+import Mobileclient from gmusicapi
import argparse
import sys
import requests
import json
+import urllib.parse
+
+creds_path = sys.path[0] + '/mobileclient.cred'
-creds_path=sys.path[0] + '/mobileclient.cred'
def authenticate(api):
creds = api.perform_oauth(storage_filepath=creds_path, open_browser=False)
-def uploadLibrary(mudbase_api, songs):
+def uploadLibrary(mudbase_api, mudbase_user, mudbase_password, songs):
+ # First, attempt to login and start a session.
+ s = requests.Session()
+ response = s.post(mudbase_api + '/login?username='
+ + urllib.parse.quote(mudbase_user)
+ + '&password='
+ + urllib.parse.quote(mudbase_password))
+
+ if response.status_code != 200:
+ print("Unable to log in to MuDBase API.")
+
# Helpers
def getArtistStoreIds(song):
if 'artistId' in song:
- return [ song['artistId'][0] ]
- return [];
+ return [song['artistId'][0]]
+ return []
+
def getSongStoreIds(song):
if 'storeId' in song:
- return [ song['storeId'] ]
+ return [song['storeId']]
return []
# Create GPM import tag
- gpmTagIdResponse = requests.post(mudbase_api + '/tag', data = {
+ gpmTagIdResponse = s.post(mudbase_api + '/tag', data={
'name': 'GPM Import'
}).json()
gpmTagId = gpmTagIdResponse['id']
print(f"Created tag \"GPM Import\", response: {gpmTagIdResponse}")
# Create the root genre tag
- genreRootResponse = requests.post(mudbase_api + '/tag', data = {
+ genreRootResponse = s.post(mudbase_api + '/tag', data={
'name': 'Genre'
}).json()
genreRootTagId = genreRootResponse['id']
@@ -47,75 +60,80 @@ def uploadLibrary(mudbase_api, songs):
# Determine artist properties.
artist = {
'name': song['artist'],
- 'storeLinks': [ 'https://play.google.com/music/m' + id for id in getArtistStoreIds(song) ],
- 'tagIds': [ gpmTagId ]
+ 'storeLinks': ['https://play.google.com/music/m' + id for id in getArtistStoreIds(song)],
+ 'tagIds': [gpmTagId]
} if 'artist' in song else None
# Determine album properties.
album = {
'name': song['album'],
- 'tagIds': [ gpmTagId ]
+ 'tagIds': [gpmTagId]
} if 'album' in song else None
# Determine genre properties.
genre = {
'name': song['genre'],
- 'parentId': genreRootTagId
+ 'parentId': genreRootTagId
} if 'genre' in song else None
# Upload artist if not already done
artistId = None
if artist:
- for key,value in storedArtists.items():
+ for key, value in storedArtists.items():
if value == artist:
artistId = key
break
if not artistId:
- response = requests.post(mudbase_api + '/artist', json = artist).json()
+ response = s.post(mudbase_api + '/artist', json=artist).json()
artistId = response['id']
- print(f"Created artist \"{artist['name']}\", response: {response}")
+ print(
+ f"Created artist \"{artist['name']}\", response: {response}")
storedArtists[artistId] = artist
# Upload album if not already done
albumId = None
if album:
- for key,value in storedAlbums.items():
+ for key, value in storedAlbums.items():
if value == album:
albumId = key
break
if not albumId:
- response = requests.post(mudbase_api + '/album', json = album).json()
+ response = s.post(mudbase_api + '/album', json=album).json()
albumId = response['id']
- print(f"Created album \"{album['name']}\", response: {response}")
+ print(
+ f"Created album \"{album['name']}\", response: {response}")
storedAlbums[albumId] = album
# Upload genre if not already done
genreTagId = None
if genre:
- for key,value in storedGenreTags.items():
+ for key, value in storedGenreTags.items():
if value == genre:
genreTagId = key
break
if not genreTagId:
- response = requests.post(mudbase_api + '/tag', json = genre).json()
+ response = s.post(mudbase_api + '/tag', json=genre).json()
genreTagId = response['id']
- print(f"Created genre tag \"Genre / {genre['name']}\", response: {response}")
+ print(
+ f"Created genre tag \"Genre / {genre['name']}\", response: {response}")
storedGenreTags[genreTagId] = genre
# Upload the song itself
- tagIds = [ gpmTagId ]
+ tagIds = [gpmTagId]
if genreTagId:
tagIds.append(genreTagId)
_song = {
'title': song['title'],
- 'artistIds': [ artistId ] if artistId != None else [],
- 'albumIds': [ albumId ] if albumId != None else [],
+ 'artistIds': [artistId] if artistId != None else [],
+ 'albumIds': [albumId] if albumId != None else [],
'tagIds': tagIds,
- 'storeLinks': [ 'https://play.google.com/music/m/' + id for id in getSongStoreIds(song) ],
+ 'storeLinks': ['https://play.google.com/music/m/' + id for id in getSongStoreIds(song)],
}
- response = requests.post(mudbase_api + '/song', json = _song).json()
- print(f"Created song \"{song['title']}\" with artist ID {artistId}, album ID {albumId}, response: {response}")
-
+ response = s.post(mudbase_api + '/song', json=_song).json()
+ print(
+ f"Created song \"{song['title']}\" with artist ID {artistId}, album ID {albumId}, response: {response}")
+
+
def getData(api):
return {
"songs": api.get_all_songs(),
@@ -125,7 +143,7 @@ def getData(api):
def getSongs(data):
# Get songs from library
- songs = [] #data['songs']
+ songs = [] # data['songs']
# Append songs from playlists
for playlist in data['playlists']:
@@ -135,16 +153,27 @@ def getSongs(data):
# Uniquify by using a dict. After all, same song may appear in
# multiple playlists.
- sI = lambda song: song['artist'] + '-' + song['title'] if 'artist' in song and 'title' in song else 'z'
+ def sI(song): return song['artist'] + '-' + \
+ song['title'] if 'artist' in song and 'title' in song else 'z'
return list(dict((sI(song), song) for song in songs).values())
+
api = Mobileclient()
-parser = argparse.ArgumentParser(description="Import Google Music library into MudBase.")
-parser.add_argument('--authenticate', help="Generate credentials for authentication", action="store_true")
-parser.add_argument('--store-to', help="Store GPM library to JSON for later upload", action='store', dest='store_to')
-parser.add_argument('--load-from', help="Load GPM library from JSON for upload", action='store', dest='load_from')
-parser.add_argument('--mudbase_api', help="Address for the Mudbase back-end API to upload to", action='store', dest='mudbase_api')
+parser = argparse.ArgumentParser(
+ description="Import Google Music library into MudBase.")
+parser.add_argument(
+ '--authenticate', help="Generate credentials for authentication", action="store_true")
+parser.add_argument('--store-to', help="Store GPM library to JSON for later upload",
+ action='store', dest='store_to')
+parser.add_argument('--load-from', help="Load GPM library from JSON for upload",
+ action='store', dest='load_from')
+parser.add_argument('--mudbase_api', help="Address for the Mudbase back-end API to upload to",
+ action='store', dest='mudbase_api')
+parser.add_argument('--mudbase_user', help="Username for the Mudbase API",
+ action='store', dest="mudbase_user")
+parser.add_argument('--mudbase_password', help="Password for the Mudbase API",
+ action='store', dest="mudbase_password")
args = parser.parse_args()
@@ -155,7 +184,8 @@ data = None
# Determine whether we need to log in to GPM and get songs
if args.store_to or (not args.load_from and args.mudbase_api):
- api.oauth_login(Mobileclient.FROM_MAC_ADDRESS, oauth_credentials=creds_path)
+ api.oauth_login(Mobileclient.FROM_MAC_ADDRESS,
+ oauth_credentials=creds_path)
data = getData(api)
# Determine whether to save to a file
@@ -172,5 +202,7 @@ songs = getSongs(data)
print(f"Found {len(songs)} songs.")
if args.mudbase_api:
- api.oauth_login(Mobileclient.FROM_MAC_ADDRESS, oauth_credentials=creds_path)
- uploadLibrary(args.mudbase_api, songs)
+ api.oauth_login(Mobileclient.FROM_MAC_ADDRESS,
+ oauth_credentials=creds_path)
+ uploadLibrary(args.mudbase_api, args.mudbase_user,
+ args.mudbase_password, songs)
diff --git a/server/app.ts b/server/app.ts
index 3155d73..2411cb3 100644
--- a/server/app.ts
+++ b/server/app.ts
@@ -2,33 +2,39 @@ const bodyParser = require('body-parser');
import * as api from '../client/src/api';
import Knex from 'knex';
-import { CreateSongEndpointHandler } from './endpoints/CreateSongEndpointHandler';
-import { CreateArtistEndpointHandler } from './endpoints/CreateArtistEndpointHandler';
-import { QueryEndpointHandler } from './endpoints/QueryEndpointHandler';
-import { ArtistDetailsEndpointHandler } from './endpoints/ArtistDetailsEndpointHandler'
-import { SongDetailsEndpointHandler } from './endpoints/SongDetailsEndpointHandler';
-import { ModifyArtistEndpointHandler } from './endpoints/ModifyArtistEndpointHandler';
-import { ModifySongEndpointHandler } from './endpoints/ModifySongEndpointHandler';
-import { CreateTagEndpointHandler } from './endpoints/CreateTagEndpointHandler';
-import { ModifyTagEndpointHandler } from './endpoints/ModifyTagEndpointHandler';
-import { TagDetailsEndpointHandler } from './endpoints/TagDetailsEndpointHandler';
-import { CreateAlbumEndpointHandler } from './endpoints/CreateAlbumEndpointHandler';
-import { ModifyAlbumEndpointHandler } from './endpoints/ModifyAlbumEndpointHandler';
-import { AlbumDetailsEndpointHandler } from './endpoints/AlbumDetailsEndpointHandler';
-import { DeleteTagEndpointHandler } from './endpoints/DeleteTagEndpointHandler';
-import { MergeTagEndpointHandler } from './endpoints/MergeTagEndpointHandler';
+import { CreateSongEndpointHandler } from './endpoints/CreateSong';
+import { CreateArtistEndpointHandler } from './endpoints/CreateArtist';
+import { QueryEndpointHandler } from './endpoints/Query';
+import { ArtistDetailsEndpointHandler } from './endpoints/ArtistDetails'
+import { SongDetailsEndpointHandler } from './endpoints/SongDetails';
+import { ModifyArtistEndpointHandler } from './endpoints/ModifyArtist';
+import { ModifySongEndpointHandler } from './endpoints/ModifySong';
+import { CreateTagEndpointHandler } from './endpoints/CreateTag';
+import { ModifyTagEndpointHandler } from './endpoints/ModifyTag';
+import { TagDetailsEndpointHandler } from './endpoints/TagDetails';
+import { CreateAlbumEndpointHandler } from './endpoints/CreateAlbum';
+import { ModifyAlbumEndpointHandler } from './endpoints/ModifyAlbum';
+import { AlbumDetailsEndpointHandler } from './endpoints/AlbumDetails';
+import { DeleteTagEndpointHandler } from './endpoints/DeleteTag';
+import { MergeTagEndpointHandler } from './endpoints/MergeTag';
+import { RegisterUserEndpointHandler } from './endpoints/RegisterUser';
import * as endpointTypes from './endpoints/types';
+import { sha512 } from 'js-sha512';
-const invokeHandler = (handler:endpointTypes.EndpointHandler, knex: Knex) => {
+// For authentication
+var passport = require('passport');
+var Strategy = require('passport-local').Strategy;
+
+const invokeHandler = (handler: endpointTypes.EndpointHandler, knex: Knex) => {
return async (req: any, res: any) => {
console.log("Incoming", req.method, " @ ", req.url);
await handler(req, res, knex)
- .catch(endpointTypes.catchUnhandledErrors)
- .catch((_e:endpointTypes.EndpointError) => {
- let e:endpointTypes.EndpointError = _e;
- console.log("Error handling request: ", e.internalMessage);
- res.sendStatus(e.httpStatus);
- })
+ .catch(endpointTypes.catchUnhandledErrors)
+ .catch((_e: endpointTypes.EndpointError) => {
+ let e: endpointTypes.EndpointError = _e;
+ console.log("Error handling request: ", e.internalMessage);
+ res.sendStatus(e.httpStatus);
+ })
console.log("Finished handling", req.method, "@", req.url);
};
}
@@ -37,26 +43,84 @@ const SetupApp = (app: any, knex: Knex, apiBaseUrl: string) => {
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
- const invokeWithKnex = (handler: endpointTypes.EndpointHandler) => {
+ // Set up auth. See: https://github.com/passport/express-4.x-local-example.git
+ passport.use(new Strategy(
+ function (email: string, password: string, cb: any) {
+ (async () => {
+ try {
+ const user = await knex.select(['email', 'passwordHash', 'id'])
+ .from('users')
+ .where({ 'email': email })
+ .then((users: any) => users[0]);
+ if (!user) { cb(null, false); }
+ if (sha512(password) != user.passwordHash) {
+ return cb(null, false);
+ }
+ return cb(null, user);
+ } catch (error) { cb(error); }
+ })();
+ }));
+ passport.serializeUser(function (user: any, cb: any) {
+ cb(null, user.id);
+ });
+ passport.deserializeUser(function (id: number, cb: any) {
+ (async () => {
+ try {
+ const user = await knex.select(['email', 'passwordHash', 'id'])
+ .from('users')
+ .where({ 'id': id })
+ .then((users: any) => users[0]);
+ if (!user) { cb(null, false); }
+ return cb(null, user);
+ } catch (error) { cb(error); }
+ })();
+ });
+
+ app.use(require('express-session')({ secret: 'EA9q5cukt7UFhN', resave: false, saveUninitialized: false }));
+ app.use(passport.initialize());
+ app.use(passport.session());
+
+ const _invoke = (handler: endpointTypes.EndpointHandler) => {
return invokeHandler(handler, knex);
}
+ const checkLogin = () => {
+ return function (req: any, res: any, next: any) {
+ if (!req.isAuthenticated || !req.isAuthenticated()) {
+ return res
+ .status(401)
+ .json({ reason: "NotLoggedIn" })
+ .send();
+ }
+ next();
+ }
+ }
+
// Set up REST API endpoints
- app.post(apiBaseUrl + api.CreateSongEndpoint, invokeWithKnex(CreateSongEndpointHandler));
- app.post(apiBaseUrl + api.QueryEndpoint, invokeWithKnex(QueryEndpointHandler));
- app.post(apiBaseUrl + api.CreateArtistEndpoint, invokeWithKnex(CreateArtistEndpointHandler));
- app.put(apiBaseUrl + api.ModifyArtistEndpoint, invokeWithKnex(ModifyArtistEndpointHandler));
- app.put(apiBaseUrl + api.ModifySongEndpoint, invokeWithKnex(ModifySongEndpointHandler));
- app.get(apiBaseUrl + api.SongDetailsEndpoint, invokeWithKnex(SongDetailsEndpointHandler));
- app.get(apiBaseUrl + api.ArtistDetailsEndpoint, invokeWithKnex(ArtistDetailsEndpointHandler));
- app.post(apiBaseUrl + api.CreateTagEndpoint, invokeWithKnex(CreateTagEndpointHandler));
- app.put(apiBaseUrl + api.ModifyTagEndpoint, invokeWithKnex(ModifyTagEndpointHandler));
- app.get(apiBaseUrl + api.TagDetailsEndpoint, invokeWithKnex(TagDetailsEndpointHandler));
- app.post(apiBaseUrl + api.CreateAlbumEndpoint, invokeWithKnex(CreateAlbumEndpointHandler));
- app.put(apiBaseUrl + api.ModifyAlbumEndpoint, invokeWithKnex(ModifyAlbumEndpointHandler));
- app.get(apiBaseUrl + api.AlbumDetailsEndpoint, invokeWithKnex(AlbumDetailsEndpointHandler));
- app.delete(apiBaseUrl + api.DeleteTagEndpoint, invokeWithKnex(DeleteTagEndpointHandler));
- app.post(apiBaseUrl + api.MergeTagEndpoint, invokeWithKnex(MergeTagEndpointHandler));
+ app.post(apiBaseUrl + api.CreateSongEndpoint, checkLogin(), _invoke(CreateSongEndpointHandler));
+ app.post(apiBaseUrl + api.QueryEndpoint, checkLogin(), _invoke(QueryEndpointHandler));
+ app.post(apiBaseUrl + api.CreateArtistEndpoint, checkLogin(), _invoke(CreateArtistEndpointHandler));
+ app.put(apiBaseUrl + api.ModifyArtistEndpoint, checkLogin(), _invoke(ModifyArtistEndpointHandler));
+ app.put(apiBaseUrl + api.ModifySongEndpoint, checkLogin(), _invoke(ModifySongEndpointHandler));
+ app.get(apiBaseUrl + api.SongDetailsEndpoint, checkLogin(), _invoke(SongDetailsEndpointHandler));
+ app.get(apiBaseUrl + api.ArtistDetailsEndpoint, checkLogin(), _invoke(ArtistDetailsEndpointHandler));
+ app.post(apiBaseUrl + api.CreateTagEndpoint, checkLogin(), _invoke(CreateTagEndpointHandler));
+ app.put(apiBaseUrl + api.ModifyTagEndpoint, checkLogin(), _invoke(ModifyTagEndpointHandler));
+ app.get(apiBaseUrl + api.TagDetailsEndpoint, checkLogin(), _invoke(TagDetailsEndpointHandler));
+ app.post(apiBaseUrl + api.CreateAlbumEndpoint, checkLogin(), _invoke(CreateAlbumEndpointHandler));
+ app.put(apiBaseUrl + api.ModifyAlbumEndpoint, checkLogin(), _invoke(ModifyAlbumEndpointHandler));
+ app.get(apiBaseUrl + api.AlbumDetailsEndpoint, checkLogin(), _invoke(AlbumDetailsEndpointHandler));
+ app.delete(apiBaseUrl + api.DeleteTagEndpoint, checkLogin(), _invoke(DeleteTagEndpointHandler));
+ app.post(apiBaseUrl + api.MergeTagEndpoint, checkLogin(), _invoke(MergeTagEndpointHandler));
+ app.post(apiBaseUrl + api.RegisterUserEndpoint, _invoke(RegisterUserEndpointHandler));
+
+ app.post('/login', passport.authenticate('local'), (req: any, res: any) => {
+ res.status(200).send({ userId: req.user.id });
+ });
+ app.post('/logout', function (req: any, res: any) {
+ req.logout();
+ res.status(200).send();
+ });
}
export { SetupApp }
\ No newline at end of file
diff --git a/server/endpoints/AlbumDetailsEndpointHandler.ts b/server/endpoints/AlbumDetails.ts
similarity index 78%
rename from server/endpoints/AlbumDetailsEndpointHandler.ts
rename to server/endpoints/AlbumDetails.ts
index e833ff9..635b210 100644
--- a/server/endpoints/AlbumDetailsEndpointHandler.ts
+++ b/server/endpoints/AlbumDetails.ts
@@ -12,6 +12,8 @@ export const AlbumDetailsEndpointHandler: EndpointHandler = async (req: any, res
throw e;
}
+ const { id: userId } = req.user;
+
try {
// Start transfers for songs, tags and artists.
// Also request the album itself.
@@ -35,6 +37,7 @@ export const AlbumDetailsEndpointHandler: EndpointHandler = async (req: any, res
});
const albumPromise = knex.select('name', 'storeLinks')
.from('albums')
+ .where({ 'user': userId })
.where({ id: req.params.id })
.then((albums: any) => albums[0]);
@@ -43,17 +46,19 @@ export const AlbumDetailsEndpointHandler: EndpointHandler = async (req: any, res
await Promise.all([albumPromise, tagIdsPromise, songIdsPromise, artistIdsPromise]);
// Respond to the request.
- console.log("ALBUM: ", album);
- const response: api.AlbumDetailsResponse = {
- name: album['name'],
- artistIds: artists,
- tagIds: tags,
- songIds: songs,
- storeLinks: asJson(album['storeLinks']),
- };
-
- await res.send(response);
+ if (album) {
+ const response: api.AlbumDetailsResponse = {
+ name: album['name'],
+ artistIds: artists,
+ tagIds: tags,
+ songIds: songs,
+ storeLinks: asJson(album['storeLinks']),
+ };
+ await res.send(response);
+ } else {
+ await res.status(404).send({});
+ }
} catch (e) {
- catchUnhandledErrors(e);
- }
+ catchUnhandledErrors(e);
+}
}
\ No newline at end of file
diff --git a/server/endpoints/ArtistDetailsEndpointHandler.ts b/server/endpoints/ArtistDetails.ts
similarity index 70%
rename from server/endpoints/ArtistDetailsEndpointHandler.ts
rename to server/endpoints/ArtistDetails.ts
index ede0836..effe19f 100644
--- a/server/endpoints/ArtistDetailsEndpointHandler.ts
+++ b/server/endpoints/ArtistDetails.ts
@@ -12,6 +12,8 @@ export const ArtistDetailsEndpointHandler: EndpointHandler = async (req: any, re
throw e;
}
+ const { id: userId } = req.user;
+
try {
const tagIds = Array.from(new Set((await knex.select('tagId')
.from('artists_tags')
@@ -20,15 +22,19 @@ export const ArtistDetailsEndpointHandler: EndpointHandler = async (req: any, re
const results = await knex.select(['id', 'name', 'storeLinks'])
.from('artists')
+ .where({ 'user': userId })
.where({ 'id': req.params.id });
- const response: api.ArtistDetailsResponse = {
- name: results[0].name,
- tagIds: tagIds,
- storeLinks: asJson(results[0].storeLinks),
+ if (results[0]) {
+ const response: api.ArtistDetailsResponse = {
+ name: results[0].name,
+ tagIds: tagIds,
+ storeLinks: asJson(results[0].storeLinks),
+ }
+ await res.send(response);
+ } else {
+ await res.status(404).send({});
}
-
- await res.send(response);
} catch (e) {
catchUnhandledErrors(e)
}
diff --git a/server/endpoints/CreateAlbumEndpointHandler.ts b/server/endpoints/CreateAlbum.ts
similarity index 93%
rename from server/endpoints/CreateAlbumEndpointHandler.ts
rename to server/endpoints/CreateAlbum.ts
index e9f9509..7172d5b 100644
--- a/server/endpoints/CreateAlbumEndpointHandler.ts
+++ b/server/endpoints/CreateAlbum.ts
@@ -11,8 +11,9 @@ export const CreateAlbumEndpointHandler: EndpointHandler = async (req: any, res:
throw e;
}
const reqObject: api.CreateAlbumRequest = req.body;
+ const { id: userId } = req.user;
- console.log("Create Album:", reqObject);
+ console.log("User ", userId, ": Create Album ", reqObject);
await knex.transaction(async (trx) => {
try {
@@ -20,6 +21,7 @@ export const CreateAlbumEndpointHandler: EndpointHandler = async (req: any, res:
const artistIdsPromise = reqObject.artistIds ?
trx.select('id')
.from('artists')
+ .where({ 'user': userId })
.whereIn('id', reqObject.artistIds)
.then((as: any) => as.map((a: any) => a['id'])) :
(async () => { return [] })();
@@ -28,6 +30,7 @@ export const CreateAlbumEndpointHandler: EndpointHandler = async (req: any, res:
const tagIdsPromise = reqObject.tagIds ?
trx.select('id')
.from('tags')
+ .where({ 'user': userId })
.whereIn('id', reqObject.tagIds)
.then((as: any) => as.map((a: any) => a['id'])) :
(async () => { return [] })();
@@ -50,6 +53,7 @@ export const CreateAlbumEndpointHandler: EndpointHandler = async (req: any, res:
.insert({
name: reqObject.name,
storeLinks: JSON.stringify(reqObject.storeLinks || []),
+ user: userId,
})
.returning('id') // Needed for Postgres
)[0];
diff --git a/server/endpoints/CreateArtistEndpointHandler.ts b/server/endpoints/CreateArtist.ts
similarity index 92%
rename from server/endpoints/CreateArtistEndpointHandler.ts
rename to server/endpoints/CreateArtist.ts
index 155e9d2..0496c47 100644
--- a/server/endpoints/CreateArtistEndpointHandler.ts
+++ b/server/endpoints/CreateArtist.ts
@@ -11,8 +11,9 @@ export const CreateArtistEndpointHandler: EndpointHandler = async (req: any, res
throw e;
}
const reqObject: api.CreateArtistRequest = req.body;
+ const { id: userId } = req.user;
- console.log("Create artist:", reqObject)
+ console.log("User ", userId, ": Create artist ", reqObject)
await knex.transaction(async (trx) => {
try {
@@ -20,13 +21,12 @@ export const CreateArtistEndpointHandler: EndpointHandler = async (req: any, res
const tags: number[] = reqObject.tagIds ?
Array.from(new Set(
(await trx.select('id').from('tags')
+ .where({ 'user': userId })
.whereIn('id', reqObject.tagIds))
.map((tag: any) => tag['id'])
))
: [];
- console.log("Found artist tags:", tags)
-
if (reqObject.tagIds && tags && tags.length !== reqObject.tagIds.length) {
const e: EndpointError = {
internalMessage: 'Not all tags exist for CreateArtist request: ' + JSON.stringify(req.body),
@@ -40,6 +40,7 @@ export const CreateArtistEndpointHandler: EndpointHandler = async (req: any, res
.insert({
name: reqObject.name,
storeLinks: JSON.stringify(reqObject.storeLinks || []),
+ user: userId,
})
.returning('id') // Needed for Postgres
)[0];
diff --git a/server/endpoints/CreateSongEndpointHandler.ts b/server/endpoints/CreateSong.ts
similarity index 93%
rename from server/endpoints/CreateSongEndpointHandler.ts
rename to server/endpoints/CreateSong.ts
index 89fdf58..a3c80c1 100644
--- a/server/endpoints/CreateSongEndpointHandler.ts
+++ b/server/endpoints/CreateSong.ts
@@ -11,8 +11,9 @@ export const CreateSongEndpointHandler: EndpointHandler = async (req: any, res:
throw e;
}
const reqObject: api.CreateSongRequest = req.body;
+ const { id: userId } = req.user;
- console.log("Create Song:", reqObject);
+ console.log("User ", userId, ": Create Song ", reqObject);
await knex.transaction(async (trx) => {
try {
@@ -20,6 +21,7 @@ export const CreateSongEndpointHandler: EndpointHandler = async (req: any, res:
const artistIdsPromise = reqObject.artistIds ?
trx.select('id')
.from('artists')
+ .where({ 'user': userId })
.whereIn('id', reqObject.artistIds)
.then((as: any) => as.map((a: any) => a['id'])) :
(async () => { return [] })();
@@ -28,6 +30,7 @@ export const CreateSongEndpointHandler: EndpointHandler = async (req: any, res:
const tagIdsPromise = reqObject.tagIds ?
trx.select('id')
.from('tags')
+ .where({ 'user': userId })
.whereIn('id', reqObject.tagIds)
.then((as: any) => as.map((a: any) => a['id'])) :
(async () => { return [] })();
@@ -36,6 +39,7 @@ export const CreateSongEndpointHandler: EndpointHandler = async (req: any, res:
const albumIdsPromise = reqObject.albumIds ?
trx.select('id')
.from('albums')
+ .where({ 'user': userId })
.whereIn('id', reqObject.albumIds)
.then((as: any) => as.map((a: any) => a['id'])) :
(async () => { return [] })();
@@ -59,6 +63,7 @@ export const CreateSongEndpointHandler: EndpointHandler = async (req: any, res:
.insert({
title: reqObject.title,
storeLinks: JSON.stringify(reqObject.storeLinks || []),
+ user: userId,
})
.returning('id') // Needed for Postgres
)[0];
diff --git a/server/endpoints/CreateTagEndpointHandler.ts b/server/endpoints/CreateTag.ts
similarity index 89%
rename from server/endpoints/CreateTagEndpointHandler.ts
rename to server/endpoints/CreateTag.ts
index 183ed1c..1587bcf 100644
--- a/server/endpoints/CreateTagEndpointHandler.ts
+++ b/server/endpoints/CreateTag.ts
@@ -11,8 +11,9 @@ export const CreateTagEndpointHandler: EndpointHandler = async (req: any, res: a
throw e;
}
const reqObject: api.CreateTagRequest = req.body;
+ const { id: userId } = req.user;
- console.log("Create Tag: ", reqObject);
+ console.log("User ", userId, ": Create Tag ", reqObject);
await knex.transaction(async (trx) => {
try {
@@ -21,6 +22,7 @@ export const CreateTagEndpointHandler: EndpointHandler = async (req: any, res: a
reqObject.parentId ?
(await trx.select('id')
.from('tags')
+ .where({ 'user': userId })
.where({ 'id': reqObject.parentId }))[0]['id'] :
undefined;
@@ -35,7 +37,8 @@ export const CreateTagEndpointHandler: EndpointHandler = async (req: any, res: a
// Create the new tag.
var tag: any = {
- name: reqObject.name
+ name: reqObject.name,
+ user: userId,
};
if (maybeParent) {
tag['parentId'] = maybeParent;
diff --git a/server/endpoints/DeleteTagEndpointHandler.ts b/server/endpoints/DeleteTag.ts
similarity index 83%
rename from server/endpoints/DeleteTagEndpointHandler.ts
rename to server/endpoints/DeleteTag.ts
index 6d0ab94..3fd9ae3 100644
--- a/server/endpoints/DeleteTagEndpointHandler.ts
+++ b/server/endpoints/DeleteTag.ts
@@ -2,13 +2,14 @@ import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex';
-async function getChildrenRecursive(id: number, trx: any) {
+async function getChildrenRecursive(id: number, userId: number, trx: any) {
const directChildren = (await trx.select('id')
.from('tags')
+ .where({ 'user': userId })
.where({ 'parentId': id })).map((r: any) => r.id);
const indirectChildrenPromises = directChildren.map(
- (child: number) => getChildrenRecursive(child, trx)
+ (child: number) => getChildrenRecursive(child, userId, trx)
);
const indirectChildrenNested = await Promise.all(indirectChildrenPromises);
const indirectChildren = indirectChildrenNested.flat();
@@ -28,19 +29,20 @@ export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: a
throw e;
}
const reqObject: api.DeleteTagRequest = req.body;
+ const { id: userId } = req.user;
- console.log("Delete Tag:", reqObject);
+ console.log("User ", userId, ": Delete Tag ", reqObject);
await knex.transaction(async (trx) => {
try {
// Start retrieving any child tags.
-
const childTagsPromise =
- getChildrenRecursive(req.params.id, trx);
+ getChildrenRecursive(req.params.id, userId, trx);
// Start retrieving the tag itself.
const tagPromise = trx.select('id')
.from('tags')
+ .where({ 'user': userId })
.where({ id: req.params.id })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined)
@@ -49,7 +51,6 @@ export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: a
// Merge all IDs.
const toDelete = [ tag, ...children ];
- console.log ("deleting tags: ", toDelete);
// Check that we found all objects we need.
if (!tag) {
@@ -62,6 +63,7 @@ export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: a
// Delete the tag and its children.
await trx('tags')
+ .where({ 'user': userId })
.whereIn('id', toDelete)
.del();
diff --git a/server/endpoints/MergeTagEndpointHandler.ts b/server/endpoints/MergeTag.ts
similarity index 90%
rename from server/endpoints/MergeTagEndpointHandler.ts
rename to server/endpoints/MergeTag.ts
index b84db4b..ed776c5 100644
--- a/server/endpoints/MergeTagEndpointHandler.ts
+++ b/server/endpoints/MergeTag.ts
@@ -11,8 +11,9 @@ export const MergeTagEndpointHandler: EndpointHandler = async (req: any, res: an
throw e;
}
const reqObject: api.DeleteTagRequest = req.body;
+ const { id: userId } = req.user;
- console.log("Merge Tag:", reqObject);
+ console.log("User ", userId, ": Merge Tag ", reqObject);
const fromId = req.params.id;
const toId = req.params.toId;
@@ -21,12 +22,14 @@ export const MergeTagEndpointHandler: EndpointHandler = async (req: any, res: an
// Start retrieving the "from" tag.
const fromTagPromise = trx.select('id')
.from('tags')
+ .where({ 'user': userId })
.where({ id: fromId })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined)
// Start retrieving the "to" tag.
const toTagPromise = trx.select('id')
.from('tags')
+ .where({ 'user': userId })
.where({ id: toId })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined)
@@ -44,6 +47,7 @@ export const MergeTagEndpointHandler: EndpointHandler = async (req: any, res: an
// Assign new tag ID to any objects referencing the to-be-merged tag.
const cPromise = trx('tags')
+ .where({ 'user': userId })
.where({ 'parentId': fromId })
.update({ 'parentId': toId });
const sPromise = trx('songs_tags')
@@ -59,6 +63,7 @@ export const MergeTagEndpointHandler: EndpointHandler = async (req: any, res: an
// Delete the original tag.
await trx('tags')
+ .where({ 'user': userId })
.where({ 'id': fromId })
.del();
diff --git a/server/endpoints/ModifyAlbumEndpointHandler.ts b/server/endpoints/ModifyAlbum.ts
similarity index 96%
rename from server/endpoints/ModifyAlbumEndpointHandler.ts
rename to server/endpoints/ModifyAlbum.ts
index 8d01eb0..829f6c2 100644
--- a/server/endpoints/ModifyAlbumEndpointHandler.ts
+++ b/server/endpoints/ModifyAlbum.ts
@@ -11,8 +11,9 @@ export const ModifyAlbumEndpointHandler: EndpointHandler = async (req: any, res:
throw e;
}
const reqObject: api.ModifyAlbumRequest = req.body;
+ const { id: userId } = req.user;
- console.log("Modify Album:", reqObject);
+ console.log("User ", userId, ": Modify Album ", reqObject);
await knex.transaction(async (trx) => {
try {
@@ -20,6 +21,7 @@ export const ModifyAlbumEndpointHandler: EndpointHandler = async (req: any, res:
// Start retrieving the album itself.
const albumPromise = trx.select('id')
.from('albums')
+ .where({ 'user': userId })
.where({ id: req.params.id })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined);
@@ -58,6 +60,7 @@ export const ModifyAlbumEndpointHandler: EndpointHandler = async (req: any, res:
if ("name" in reqObject) { update["name"] = reqObject.name; }
if ("storeLinks" in reqObject) { update["storeLinks"] = JSON.stringify(reqObject.storeLinks || []); }
const modifyAlbumPromise = trx('albums')
+ .where({ 'user': userId })
.where({ 'id': req.params.id })
.update(update)
diff --git a/server/endpoints/ModifyArtistEndpointHandler.ts b/server/endpoints/ModifyArtist.ts
similarity index 95%
rename from server/endpoints/ModifyArtistEndpointHandler.ts
rename to server/endpoints/ModifyArtist.ts
index b0cc77e..3d56ee6 100644
--- a/server/endpoints/ModifyArtistEndpointHandler.ts
+++ b/server/endpoints/ModifyArtist.ts
@@ -11,7 +11,9 @@ export const ModifyArtistEndpointHandler: EndpointHandler = async (req: any, res
throw e;
}
const reqObject: api.ModifyArtistRequest = req.body;
- console.log("Modify Artist:", reqObject);
+ const { id: userId } = req.user;
+
+ console.log("User ", userId, ": Modify Artist ", reqObject);
await knex.transaction(async (trx) => {
try {
@@ -20,6 +22,7 @@ export const ModifyArtistEndpointHandler: EndpointHandler = async (req: any, res
// Start retrieving the artist itself.
const artistPromise = trx.select('id')
.from('artists')
+ .where({ 'user': userId })
.where({ id: artistId })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined)
@@ -49,6 +52,7 @@ export const ModifyArtistEndpointHandler: EndpointHandler = async (req: any, res
if ("name" in reqObject) { update["name"] = reqObject.name; }
if ("storeLinks" in reqObject) { update["storeLinks"] = JSON.stringify(reqObject.storeLinks || []); }
const modifyArtistPromise = trx('artists')
+ .where({ 'user': userId })
.where({ 'id': artistId })
.update(update)
diff --git a/server/endpoints/ModifySongEndpointHandler.ts b/server/endpoints/ModifySong.ts
similarity index 97%
rename from server/endpoints/ModifySongEndpointHandler.ts
rename to server/endpoints/ModifySong.ts
index 9a280f4..488b72f 100644
--- a/server/endpoints/ModifySongEndpointHandler.ts
+++ b/server/endpoints/ModifySong.ts
@@ -11,14 +11,16 @@ export const ModifySongEndpointHandler: EndpointHandler = async (req: any, res:
throw e;
}
const reqObject: api.ModifySongRequest = req.body;
+ const { id: userId } = req.user;
- console.log("Modify Song:", reqObject);
+ console.log("User ", userId, ": Modify Song ", reqObject);
await knex.transaction(async (trx) => {
try {
// Retrieve the song to be modified itself.
const songPromise = trx.select('id')
.from('songs')
+ .where({ 'user': userId })
.where({ id: req.params.id })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined)
@@ -67,6 +69,7 @@ export const ModifySongEndpointHandler: EndpointHandler = async (req: any, res:
if ("title" in reqObject) { update["title"] = reqObject.title; }
if ("storeLinks" in reqObject) { update["storeLinks"] = JSON.stringify(reqObject.storeLinks || []); }
const modifySongPromise = trx('songs')
+ .where({ 'user': userId })
.where({ 'id': req.params.id })
.update(update)
diff --git a/server/endpoints/ModifyTagEndpointHandler.ts b/server/endpoints/ModifyTag.ts
similarity index 90%
rename from server/endpoints/ModifyTagEndpointHandler.ts
rename to server/endpoints/ModifyTag.ts
index adfb558..6596958 100644
--- a/server/endpoints/ModifyTagEndpointHandler.ts
+++ b/server/endpoints/ModifyTag.ts
@@ -11,8 +11,9 @@ export const ModifyTagEndpointHandler: EndpointHandler = async (req: any, res: a
throw e;
}
const reqObject: api.ModifyTagRequest = req.body;
+ const { id: userId } = req.user;
- console.log("Modify Tag:", reqObject);
+ console.log("User ", userId, ": Modify Tag ", reqObject);
await knex.transaction(async (trx) => {
try {
@@ -20,6 +21,7 @@ export const ModifyTagEndpointHandler: EndpointHandler = async (req: any, res: a
const parentTagPromise = reqObject.parentId ?
trx.select('id')
.from('tags')
+ .where({ 'user': userId })
.where({ 'id': reqObject.parentId })
.then((ts: any) => ts.map((t: any) => t['tagId'])) :
(async () => { return [] })();
@@ -27,6 +29,7 @@ export const ModifyTagEndpointHandler: EndpointHandler = async (req: any, res: a
// Start retrieving the tag itself.
const tagPromise = trx.select('id')
.from('tags')
+ .where({ 'user': userId })
.where({ id: req.params.id })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined)
@@ -45,6 +48,7 @@ export const ModifyTagEndpointHandler: EndpointHandler = async (req: any, res: a
// Modify the tag.
await trx('tags')
+ .where({ 'user': userId })
.where({ 'id': req.params.id })
.update({
name: reqObject.name,
diff --git a/server/endpoints/QueryEndpointHandler.ts b/server/endpoints/Query.ts
similarity index 92%
rename from server/endpoints/QueryEndpointHandler.ts
rename to server/endpoints/Query.ts
index 89fb35d..60111f3 100644
--- a/server/endpoints/QueryEndpointHandler.ts
+++ b/server/endpoints/Query.ts
@@ -178,7 +178,7 @@ const objectColumns = {
[ObjectType.Tag]: ['tags.id as tags.id', 'tags.name as tags.name', 'tags.parentId as tags.parentId']
};
-function constructQuery(knex: Knex, queryFor: ObjectType, queryElem: api.QueryElem, ordering: api.Ordering,
+function constructQuery(knex: Knex, userId: number, queryFor: ObjectType, queryElem: api.QueryElem, ordering: api.Ordering,
offset: number, limit: number | null) {
const joinObjects = getRequiredDatabaseObjects(queryElem);
joinObjects.delete(queryFor); // We are already querying this object in the base query.
@@ -194,6 +194,7 @@ function constructQuery(knex: Knex, queryFor: ObjectType, queryElem: api.QueryEl
// First, we create a base query for the type of object we need to yield.
var q = knex.select(columns)
+ .where({ [objectTables[queryFor] + '.user']: userId })
.groupBy(objectTables[queryFor] + '.' + 'id')
.from(objectTables[queryFor]);
@@ -223,7 +224,7 @@ function constructQuery(knex: Knex, queryFor: ObjectType, queryElem: api.QueryEl
return q;
}
-async function getLinkedObjects(knex: Knex, base: ObjectType, linked: ObjectType, baseIds: number[]) {
+async function getLinkedObjects(knex: Knex, userId: number, base: ObjectType, linked: ObjectType, baseIds: number[]) {
var result: Record = {};
const otherTable = objectTables[linked];
const linkingTable = getLinkingTable(base, linked);
@@ -232,6 +233,7 @@ async function getLinkedObjects(knex: Knex, base: ObjectType, linked: ObjectType
await Promise.all(baseIds.map((baseId: number) => {
return knex.select(columns).groupBy(otherTable + '.id').from(otherTable)
.join(linkingTable, { [linkingTable + '.' + linkingTableIdNames[linked]]: otherTable + '.id' })
+ .where({ [otherTable + '.user']: userId })
.where({ [linkingTable + '.' + linkingTableIdNames[base]]: baseId })
.then((others: any) => { result[baseId] = others; })
}))
@@ -241,11 +243,12 @@ async function getLinkedObjects(knex: Knex, base: ObjectType, linked: ObjectType
}
// Resolve a tag into the full nested structure of its ancestors.
-async function getFullTag(knex: Knex, tag: any): Promise {
+async function getFullTag(knex: Knex, userId: number, tag: any): Promise {
const resolveTag = async (t: any) => {
if (t['tags.parentId']) {
const parent = (await knex.select(objectColumns[ObjectType.Tag])
.from('tags')
+ .where({ 'user': userId })
.where({ [objectTables[ObjectType.Tag] + '.id']: t['tags.parentId'] }))[0];
t.parent = await resolveTag(parent);
}
@@ -264,7 +267,9 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any,
throw e;
}
const reqObject: api.QueryRequest = req.body;
- console.log("Query: ", reqObject);
+ const { id: userId } = req.user;
+
+ console.log("User ", userId, ": Query ", reqObject);
try {
const songLimit = reqObject.offsetsLimits.songLimit;
@@ -278,6 +283,7 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any,
const artistsPromise: Promise = (artistLimit && artistLimit !== 0) ?
constructQuery(knex,
+ userId,
ObjectType.Artist,
reqObject.query,
reqObject.ordering,
@@ -288,6 +294,7 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any,
const albumsPromise: Promise = (albumLimit && albumLimit !== 0) ?
constructQuery(knex,
+ userId,
ObjectType.Album,
reqObject.query,
reqObject.ordering,
@@ -298,6 +305,7 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any,
const songsPromise: Promise = (songLimit && songLimit !== 0) ?
constructQuery(knex,
+ userId,
ObjectType.Song,
reqObject.query,
reqObject.ordering,
@@ -308,6 +316,7 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any,
const tagsPromise: Promise = (tagLimit && tagLimit !== 0) ?
constructQuery(knex,
+ userId,
ObjectType.Tag,
reqObject.query,
reqObject.ordering,
@@ -325,18 +334,18 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any,
})();
const songsArtistsPromise: Promise> = (songLimit && songLimit !== 0) ?
(async () => {
- return await getLinkedObjects(knex, ObjectType.Song, ObjectType.Artist, await songIdsPromise);
+ return await getLinkedObjects(knex, userId, ObjectType.Song, ObjectType.Artist, await songIdsPromise);
})() :
(async () => { return {}; })();
const songsTagsPromise: Promise> = (songLimit && songLimit !== 0) ?
(async () => {
- const tagsPerSong: Record = await getLinkedObjects(knex, ObjectType.Song, ObjectType.Tag, await songIdsPromise);
+ const tagsPerSong: Record = await getLinkedObjects(knex, userId, ObjectType.Song, ObjectType.Tag, await songIdsPromise);
var result: Record = {};
for (var key in tagsPerSong) {
const tags = tagsPerSong[key];
var fullTags: any[] = [];
for (var idx in tags) {
- fullTags.push(await getFullTag(knex, tags[idx]));
+ fullTags.push(await getFullTag(knex, userId, tags[idx]));
}
result[key] = fullTags;
}
@@ -345,7 +354,7 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any,
(async () => { return {}; })();
const songsAlbumsPromise: Promise> = (songLimit && songLimit !== 0) ?
(async () => {
- return await getLinkedObjects(knex, ObjectType.Song, ObjectType.Album, await songIdsPromise);
+ return await getLinkedObjects(knex, userId, ObjectType.Song, ObjectType.Album, await songIdsPromise);
})() :
(async () => { return {}; })();
diff --git a/server/endpoints/RegisterUser.ts b/server/endpoints/RegisterUser.ts
new file mode 100644
index 0000000..0782d90
--- /dev/null
+++ b/server/endpoints/RegisterUser.ts
@@ -0,0 +1,49 @@
+import * as api from '../../client/src/api';
+import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
+import Knex from 'knex';
+
+import { sha512 } from 'js-sha512';
+
+export const RegisterUserEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
+ if (!api.checkRegisterUserRequest(req)) {
+ const e: EndpointError = {
+ internalMessage: 'Invalid RegisterUser request: ' + JSON.stringify(req.body),
+ httpStatus: 400
+ };
+ throw e;
+ }
+ const reqObject: api.RegisterUserRequest = req.body;
+
+ console.log("Register User: ", reqObject);
+
+ await knex.transaction(async (trx) => {
+ try {
+ // check if the user already exists
+ const user = (await trx
+ .select('id')
+ .from('users')
+ .where({ email: reqObject.email }))[0];
+ if(user) {
+ res.status(400).send();
+ return;
+ }
+
+ // Create the new user.
+ const passwordHash = sha512(reqObject.password);
+ const userId = (await trx('users')
+ .insert({
+ email: reqObject.email,
+ passwordHash: passwordHash,
+ })
+ .returning('id') // Needed for Postgres
+ )[0];
+
+ // Respond to the request.
+ res.status(200).send();
+
+ } catch (e) {
+ catchUnhandledErrors(e);
+ trx.rollback();
+ }
+ })
+}
\ No newline at end of file
diff --git a/server/endpoints/SongDetailsEndpointHandler.ts b/server/endpoints/SongDetails.ts
similarity index 80%
rename from server/endpoints/SongDetailsEndpointHandler.ts
rename to server/endpoints/SongDetails.ts
index 591e31a..24e6267 100644
--- a/server/endpoints/SongDetailsEndpointHandler.ts
+++ b/server/endpoints/SongDetails.ts
@@ -12,6 +12,8 @@ export const SongDetailsEndpointHandler: EndpointHandler = async (req: any, res:
throw e;
}
+ const { id: userId } = req.user;
+
try {
const tagIdsPromise: Promise = knex.select('tagId')
.from('songs_tags')
@@ -41,21 +43,25 @@ export const SongDetailsEndpointHandler: EndpointHandler = async (req: any, res:
})
const songPromise = await knex.select(['id', 'title', 'storeLinks'])
.from('songs')
+ .where({ 'user': userId })
.where({ 'id': req.params.id })
.then((ss: any) => ss[0])
const [tags, albums, artists, song] =
await Promise.all([tagIdsPromise, albumIdsPromise, artistIdsPromise, songPromise]);
- const response: api.SongDetailsResponse = {
- title: song.title,
- tagIds: tags,
- artistIds: artists,
- albumIds: albums,
- storeLinks: asJson(song.storeLinks),
+ if (song) {
+ const response: api.SongDetailsResponse = {
+ title: song.title,
+ tagIds: tags,
+ artistIds: artists,
+ albumIds: albums,
+ storeLinks: asJson(song.storeLinks),
+ }
+ await res.send(response);
+ } else {
+ await res.status(404).send({});
}
-
- await res.send(response);
} catch (e) {
catchUnhandledErrors(e)
}
diff --git a/server/endpoints/TagDetailsEndpointHandler.ts b/server/endpoints/TagDetails.ts
similarity index 65%
rename from server/endpoints/TagDetailsEndpointHandler.ts
rename to server/endpoints/TagDetails.ts
index 7497acb..daacaae 100644
--- a/server/endpoints/TagDetailsEndpointHandler.ts
+++ b/server/endpoints/TagDetails.ts
@@ -11,17 +11,23 @@ export const TagDetailsEndpointHandler: EndpointHandler = async (req: any, res:
throw e;
}
+ const { id: userId } = req.user;
+
try {
const results = await knex.select(['id', 'name', 'parentId'])
.from('tags')
+ .where({ 'user': userId })
.where({ 'id': req.params.id });
- const response: api.TagDetailsResponse = {
- name: results[0].name,
- parentId: results[0].parentId,
+ if (results[0]) {
+ const response: api.TagDetailsResponse = {
+ name: results[0].name,
+ parentId: results[0].parentId || undefined,
+ }
+ await res.send(response);
+ } else {
+ await res.status(404).send({});
}
-
- await res.send(response);
} catch (e) {
catchUnhandledErrors(e)
}
diff --git a/server/migrations/20201110170100_add_users.ts b/server/migrations/20201110170100_add_users.ts
new file mode 100644
index 0000000..6b75776
--- /dev/null
+++ b/server/migrations/20201110170100_add_users.ts
@@ -0,0 +1,73 @@
+import * as Knex from "knex";
+import { sha512 } from "js-sha512";
+
+
+export async function up(knex: Knex): Promise {
+ // Users table.
+ await knex.schema.createTable(
+ 'users',
+ (table: any) => {
+ table.increments('id');
+ table.string('email');
+ table.string('passwordHash')
+ }
+ )
+
+ // Add user column to other object tables.
+ await knex.schema.alterTable(
+ 'songs',
+ (table: any) => {
+ table.integer('user').unsigned().notNullable().defaultTo(1);
+ }
+ )
+ await knex.schema.alterTable(
+ 'albums',
+ (table: any) => {
+ table.integer('user').unsigned().notNullable().defaultTo(1);
+ }
+ )
+ await knex.schema.alterTable(
+ 'tags',
+ (table: any) => {
+ table.integer('user').unsigned().notNullable().defaultTo(1);
+ }
+ )
+ await knex.schema.alterTable(
+ 'artists',
+ (table: any) => {
+ table.integer('user').unsigned().notNullable().defaultTo(1);
+ }
+ )
+}
+
+
+export async function down(knex: Knex): Promise {
+ await knex.schema.dropTable('users');
+
+ // Remove the user column
+ await knex.schema.alterTable(
+ 'songs',
+ (table: any) => {
+ table.dropColumn('user');
+ }
+ )
+ await knex.schema.alterTable(
+ 'albums',
+ (table: any) => {
+ table.dropColumn('user');
+ }
+ )
+ await knex.schema.alterTable(
+ 'tags',
+ (table: any) => {
+ table.dropColumn('user');
+ }
+ )
+ await knex.schema.alterTable(
+ 'artists',
+ (table: any) => {
+ table.dropColumn('user');
+ }
+ )
+}
+
diff --git a/server/package-lock.json b/server/package-lock.json
index 6b6e850..a52e71e 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -1936,6 +1936,11 @@
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz",
"integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA=="
},
+ "js-sha512": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz",
+ "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ=="
+ },
"jsbi": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.3.tgz",
@@ -2756,6 +2761,28 @@
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
},
+ "passport": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz",
+ "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==",
+ "requires": {
+ "passport-strategy": "1.x.x",
+ "pause": "0.0.1"
+ }
+ },
+ "passport-local": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
+ "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=",
+ "requires": {
+ "passport-strategy": "1.x.x"
+ }
+ },
+ "passport-strategy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
+ "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
+ },
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -2789,6 +2816,11 @@
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA="
},
+ "pause": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
+ "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
+ },
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
diff --git a/server/package.json b/server/package.json
index 11035c4..a16940c 100644
--- a/server/package.json
+++ b/server/package.json
@@ -13,12 +13,15 @@
"chai-http": "^4.3.0",
"express": "^4.16.4",
"jasmine": "^3.5.0",
+ "js-sha512": "^0.8.0",
"knex": "^0.21.5",
"mssql": "^6.2.1",
"mysql": "^2.18.1",
"mysql2": "^2.1.0",
"nodemon": "^2.0.4",
"oracledb": "^5.0.0",
+ "passport": "^0.4.1",
+ "passport-local": "^1.0.0",
"pg": "^8.3.3",
"sqlite3": "^5.0.0",
"ts-node": "^8.10.2",
diff --git a/server/test/integration/flows/AlbumFlow.js b/server/test/integration/flows/AlbumFlow.js
index 2d76b99..719db34 100644
--- a/server/test/integration/flows/AlbumFlow.js
+++ b/server/test/integration/flows/AlbumFlow.js
@@ -4,92 +4,100 @@ const express = require('express');
import { SetupApp } from '../../../app';
import { expect } from 'chai';
import * as helpers from './helpers';
+import { sha512 } from 'js-sha512';
async function init() {
chai.use(chaiHttp);
const app = express();
- SetupApp(app, await helpers.initTestDB(), '');
- return app;
+ const knex = await helpers.initTestDB();
+
+ // Add test users.
+ await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users');
+ await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users');
+
+ SetupApp(app, knex, '');
+
+ // Login as a test user.
+ var agent = chai.request.agent(app);
+ await agent
+ .post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1'))
+ .send({});
+ return agent;
}
describe('POST /album with no name', () => {
- it('should fail', done => {
- init().then((app) => {
- chai
- .request(app)
- .post('/album')
- .send({})
- .then((res) => {
- expect(res).to.have.status(400);
- done();
- });
- });
+ it('should fail', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createAlbum(req, {}, 400);
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /album with a correct request', () => {
- it('should succeed', done => {
- init().then((app) => {
- chai
- .request(app)
- .post('/album')
- .send({
- name: "MyAlbum"
- })
- .then((res) => {
- expect(res).to.have.status(200);
- expect(res.body).to.deep.equal({
- id: 1
- });
- done();
- });
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createAlbum(req, { name: "MyAlbum" }, 200, { id: 1 });
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('PUT /album on nonexistent album', () => {
- it('should fail', done => {
- init().then((app) => {
- chai
- .request(app)
- .put('/album/1')
- .send({
- id: 1,
- name: "NewAlbumName"
- })
- .then((res) => {
- expect(res).to.have.status(400);
- done();
- })
- })
+ it('should fail', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.modifyAlbum(req, 1, { id: 1, name: "NewAlbumName" }, 400);
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('PUT /album with an existing album', () => {
- it('should succeed', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createAlbum(req, { name: "MyAlbum" }, 200, { id: 1 })
- .then(() => helpers.modifyAlbum(req, 1, { name: "MyNewAlbum" }, 200))
- .then(() => helpers.checkAlbum(req, 1, 200, { name: "MyNewAlbum", storeLinks: [], tagIds: [], songIds: [], artistIds: [] }))
- .then(req.close)
- .then(done);
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createAlbum(req, { name: "MyAlbum" }, 200, { id: 1 });
+ await helpers.modifyAlbum(req, 1, { name: "MyNewAlbum" }, 200);
+ await helpers.checkAlbum(req, 1, 200, { name: "MyNewAlbum", storeLinks: [], tagIds: [], songIds: [], artistIds: [] });
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /album with tags', () => {
- it('should succeed', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createTag(req, { name: "Root" }, 200, { id: 1 })
- .then(() => helpers.createTag(req, { name: "Leaf", parentId: 1 }, 200, { id: 2 }))
- .then(() => helpers.createAlbum(req, { name: "MyAlbum", tagIds: [ 1, 2 ] }, 200, { id: 1 }))
- .then(() => helpers.checkAlbum(req, 1, 200, { name: "MyAlbum", storeLinks: [], tagIds: [ 1, 2 ], songIds: [], artistIds: [] }))
- .then(req.close)
- .then(done);
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createTag(req, { name: "Root" }, 200, { id: 1 })
+ await helpers.createTag(req, { name: "Leaf", parentId: 1 }, 200, { id: 2 })
+ await helpers.createAlbum(req, { name: "MyAlbum", tagIds: [1, 2] }, 200, { id: 1 })
+ await helpers.checkAlbum(req, 1, 200, { name: "MyAlbum", storeLinks: [], tagIds: [1, 2], songIds: [], artistIds: [] })
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
diff --git a/server/test/integration/flows/ArtistFlow.js b/server/test/integration/flows/ArtistFlow.js
index 55a4e9b..04926eb 100644
--- a/server/test/integration/flows/ArtistFlow.js
+++ b/server/test/integration/flows/ArtistFlow.js
@@ -2,85 +2,101 @@ const chai = require('chai');
const chaiHttp = require('chai-http');
const express = require('express');
import { SetupApp } from '../../../app';
-import { expect } from 'chai';
import * as helpers from './helpers';
+import { sha512 } from 'js-sha512';
async function init() {
chai.use(chaiHttp);
const app = express();
- SetupApp(app, await helpers.initTestDB(), '');
- return app;
+ const knex = await helpers.initTestDB();
+
+ // Add test users.
+ await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users');
+ await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users');
+
+ SetupApp(app, knex, '');
+
+ // Login as a test user.
+ var agent = chai.request.agent(app);
+ await agent
+ .post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1'))
+ .send({});
+ return agent;
}
describe('POST /artist with no name', () => {
- it('should fail', done => {
- init().then((app) => {
- chai
- .request(app)
- .post('/artist')
- .send({})
- .then((res) => {
- expect(res).to.have.status(400);
- done();
- });
- });
+ it('should fail', async done => {
+ let agent = await init();
+ var req = agent.keepOpen();
+ try {
+ await helpers.createArtist(req, {}, 400);
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /artist with a correct request', () => {
- it('should succeed', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createArtist(req, { name: "MyArtist" }, 200, { id: 1 })
- .then(() => helpers.checkArtist(req, 1, 200, { name: "MyArtist", storeLinks: [], tagIds: [] }))
- .then(req.close)
- .then(done);
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ var req = agent.keepOpen();
+ try {
+ await helpers.createArtist(req, { name: "MyArtist" }, 200, { id: 1 });
+ await helpers.checkArtist(req, 1, 200, { name: "MyArtist", storeLinks: [], tagIds: [] });
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('PUT /artist on nonexistent artist', () => {
- it('should fail', done => {
- init().then((app) => {
- chai
- .request(app)
- .put('/artist/0')
- .send({
- id: 0,
- name: "NewArtistName"
- })
- .then((res) => {
- expect(res).to.have.status(400);
- done();
- })
- })
+ it('should fail', async done => {
+ let agent = await init();
+ var req = agent.keepOpen();
+ try {
+ await helpers.modifyArtist(req, 0, { id: 0, name: "NewArtistName" }, 400)
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('PUT /artist with an existing artist', () => {
- it('should succeed', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createArtist(req, { name: "MyArtist" }, 200, { id: 1 })
- .then(() => helpers.modifyArtist(req, 1, { name: "MyNewArtist" }, 200))
- .then(() => helpers.checkArtist(req, 1, 200, { name: "MyNewArtist", storeLinks: [], tagIds: [] }))
- .then(req.close)
- .then(done);
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ var req = agent.keepOpen();
+ try {
+ await helpers.createArtist(req, { name: "MyArtist" }, 200, { id: 1 });
+ await helpers.modifyArtist(req, 1, { name: "MyNewArtist" }, 200);
+ await helpers.checkArtist(req, 1, 200, { name: "MyNewArtist", storeLinks: [], tagIds: [] });
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /artist with tags', () => {
- it('should succeed', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createTag(req, { name: "Root" }, 200, { id: 1 })
- .then(() => helpers.createTag(req, { name: "Leaf", parentId: 1 }, 200, { id: 2 }))
- .then(() => helpers.createArtist(req, { name: "MyArtist", tagIds: [ 1, 2 ] }, 200, { id: 1 }))
- .then(() => helpers.checkArtist(req, 1, 200, { name: "MyArtist", storeLinks: [], tagIds: [ 1, 2 ] }))
- .then(req.close)
- .then(done);
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ var req = agent.keepOpen();
+ try {
+ await helpers.createTag(req, { name: "Root" }, 200, { id: 1 });
+ await helpers.createTag(req, { name: "Leaf", parentId: 1 }, 200, { id: 2 });
+ await helpers.createArtist(req, { name: "MyArtist", tagIds: [1, 2] }, 200, { id: 1 });
+ await helpers.checkArtist(req, 1, 200, { name: "MyArtist", storeLinks: [], tagIds: [1, 2] });
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
diff --git a/server/test/integration/flows/AuthFlow.js b/server/test/integration/flows/AuthFlow.js
new file mode 100644
index 0000000..61f751d
--- /dev/null
+++ b/server/test/integration/flows/AuthFlow.js
@@ -0,0 +1,145 @@
+const chai = require('chai');
+const chaiHttp = require('chai-http');
+const express = require('express');
+import { SetupApp } from '../../../app';
+import * as helpers from './helpers';
+
+async function init() {
+ chai.use(chaiHttp);
+ const app = express();
+ const knex = await helpers.initTestDB();
+
+ SetupApp(app, knex, '');
+
+ // Login as a test user.
+ var agent = chai.request.agent(app);
+ return agent;
+}
+
+describe('Auth registration password and email constraints', () => {
+ it('are enforced', async done => {
+ let req = await init();
+ try {
+ await helpers.createUser(req, "someone", "password1A!", 400); //no valid email
+ await helpers.createUser(req, "someone@email.com", "password1A", 400); //no special char
+ await helpers.createUser(req, "someone@email.com", "password1!", 400); //no capital letter
+ await helpers.createUser(req, "someone@email.com", "passwordA!", 400); //no number
+ await helpers.createUser(req, "someone@email.com", "Ϭassword1A!", 400); //non-ASCII in password
+ await helpers.createUser(req, "Ϭomeone@email.com", "password1A!", 400); //non-ASCII in email
+ await helpers.createUser(req, "someone@email.com", "pass1A!", 400); //password too short
+ await helpers.createUser(req, "someone@email.com", "password1A!", 200);
+ } finally {
+ req.close();
+ done();
+ }
+ });
+});
+
+describe('Attempting to register an already registered user', () => {
+ it('should fail', async done => {
+ let req = await init();
+ try {
+ await helpers.createUser(req, "someone@email.com", "password1A!", 200);
+ await helpers.createUser(req, "someone@email.com", "password1A!", 400);
+ } finally {
+ req.close();
+ done();
+ }
+ });
+});
+
+describe('Auth login access for users', () => {
+ it('is correctly enforced', async done => {
+ let req = await init();
+ try {
+ await helpers.createUser(req, "someone@email.com", "password1A!", 200);
+ await helpers.createUser(req, "someoneelse@other.com", "password2B!", 200);
+ await helpers.login(req, "someone@email.com", "password2B!", 401);
+ await helpers.login(req, "someoneelse@other.com", "password1A!", 401);
+ await helpers.login(req, "someone@email.com", "password1A!", 200);
+ await helpers.login(req, "someoneelse@other.com", "password2B!", 200);
+ } finally {
+ req.close();
+ done();
+ }
+ });
+});
+
+describe('Auth access to objects', () => {
+ it('is only possible when logged in', async done => {
+ let req = await init();
+ try {
+ await helpers.createUser(req, "someone@email.com", "password1A!", 200);
+ await helpers.login(req, "someone@email.com", "password1A!", 200);
+
+ await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 });
+ await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1} );
+ await helpers.createAlbum(req, { name: "Album1" }, 200, { id: 1 });
+ await helpers.createSong(req, { title: "Song1" }, 200, { id: 1 });
+
+ await helpers.checkTag(req, 1, 200);
+ await helpers.checkAlbum(req, 1, 200);
+ await helpers.checkArtist(req, 1, 200);
+ await helpers.checkSong(req, 1, 200);
+
+ await helpers.logout(req, 200);
+
+ await helpers.checkTag(req, 1, 401);
+ await helpers.checkAlbum(req, 1, 401);
+ await helpers.checkArtist(req, 1, 401);
+ await helpers.checkSong(req, 1, 401);
+ } finally {
+ req.close();
+ done();
+ }
+ });
+});
+
+describe('Auth access to user objects', () => {
+ it('is restricted to each user', async done => {
+ let req = await init();
+ try {
+ await helpers.createUser(req, "someone@email.com", "password1A!", 200);
+ await helpers.createUser(req, "someoneelse@other.com", "password2B!", 200);
+
+ await helpers.login(req, "someone@email.com", "password1A!", 200);
+ await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 });
+ await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1} );
+ await helpers.createAlbum(req, { name: "Album1" }, 200, { id: 1 });
+ await helpers.createSong(req, { title: "Song1" }, 200, { id: 1 });
+ await helpers.logout(req, 200);
+
+ await helpers.login(req, "someoneelse@other.com", "password2B!", 200);
+ await helpers.createTag(req, { name: "Tag2" }, 200, { id: 2 });
+ await helpers.createArtist(req, { name: "Artist2" }, 200, { id: 2 } );
+ await helpers.createAlbum(req, { name: "Album2" }, 200, { id: 2 });
+ await helpers.createSong(req, { title: "Song2" }, 200, { id: 2 });
+ await helpers.logout(req, 200);
+
+ await helpers.login(req, "someone@email.com", "password1A!", 200);
+ await helpers.checkTag(req, 2, 404);
+ await helpers.checkAlbum(req, 2, 404);
+ await helpers.checkArtist(req, 2, 404);
+ await helpers.checkSong(req, 2, 404);
+ await helpers.checkTag(req, 1, 200);
+ await helpers.checkAlbum(req, 1, 200);
+ await helpers.checkArtist(req, 1, 200);
+ await helpers.checkSong(req, 1, 200);
+ await helpers.logout(req, 200);
+
+ await helpers.login(req, "someoneelse@other.com", "password2B!", 200);
+ await helpers.checkTag(req, 1, 404);
+ await helpers.checkAlbum(req, 1, 404);
+ await helpers.checkArtist(req, 1, 404);
+ await helpers.checkSong(req, 1, 404);
+ await helpers.checkTag(req, 2, 200);
+ await helpers.checkAlbum(req, 2, 200);
+ await helpers.checkArtist(req, 2, 200);
+ await helpers.checkSong(req, 2, 200);
+ await helpers.logout(req, 200);
+ } finally {
+ req.close();
+ done();
+ }
+ });
+});
\ No newline at end of file
diff --git a/server/test/integration/flows/QueryFlow.js b/server/test/integration/flows/QueryFlow.js
index 242ba38..b71afd3 100644
--- a/server/test/integration/flows/QueryFlow.js
+++ b/server/test/integration/flows/QueryFlow.js
@@ -4,19 +4,32 @@ const express = require('express');
import { SetupApp } from '../../../app';
import { expect } from 'chai';
import * as helpers from './helpers';
+import { sha512 } from 'js-sha512';
async function init() {
chai.use(chaiHttp);
const app = express();
- SetupApp(app, await helpers.initTestDB(), '');;
- return app;
+ const knex = await helpers.initTestDB();
+
+ // Add test users.
+ await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users');
+ await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users');
+
+ SetupApp(app, knex, '');
+
+ // Login as a test user.
+ var agent = chai.request.agent(app);
+ await agent
+ .post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1'))
+ .send({});
+ return agent;
}
describe('POST /query with no songs', () => {
- it('should give empty list', done => {
- init().then((app) => {
- chai
- .request(app)
+ it('should give empty list', async done => {
+ let agent = await init();
+ try {
+ let res = await agent
.post('/query')
.send({
'query': {},
@@ -31,241 +44,243 @@ describe('POST /query with no songs', () => {
'ascending': true
}
})
+ expect(res).to.have.status(200);
+ expect(res.body).to.deep.equal({
+ songs: [],
+ tags: [],
+ artists: [],
+ albums: [],
+ });
+ } finally {
+ agent.close();
+ done();
+ }
+ });
+});
+
+describe('POST /query with several songs and filters', () => {
+ it('should give all correct results', async done => {
+ const song1 = {
+ songId: 1,
+ title: 'Song1',
+ storeLinks: [],
+ artists: [
+ {
+ artistId: 1,
+ name: 'Artist1',
+ storeLinks: [],
+ }
+ ],
+ tags: [],
+ albums: []
+ };
+ const song2 = {
+ songId: 2,
+ title: 'Song2',
+ storeLinks: [],
+ artists: [
+ {
+ artistId: 1,
+ name: 'Artist1',
+ storeLinks: [],
+ }
+ ],
+ tags: [],
+ albums: []
+ };
+ const song3 = {
+ songId: 3,
+ title: 'Song3',
+ storeLinks: [],
+ artists: [
+ {
+ artistId: 2,
+ name: 'Artist2',
+ storeLinks: [],
+ }
+ ],
+ tags: [],
+ albums: []
+ };
+
+ async function checkAllSongs(req) {
+ await req
+ .post('/query')
+ .send({
+ "query": {},
+ 'offsetsLimits': {
+ 'songOffset': 0,
+ 'songLimit': 10,
+ },
+ 'ordering': {
+ 'orderBy': {
+ 'type': 0,
+ },
+ 'ascending': true
+ }
+ })
.then((res) => {
expect(res).to.have.status(200);
expect(res.body).to.deep.equal({
- songs: [],
- tags: [],
+ songs: [song1, song2, song3],
artists: [],
+ tags: [],
albums: [],
});
- done();
});
- })
- });
-});
+ }
-describe('POST /query with several songs and filters', () => {
- it('should give all correct results', done => {
- init().then((app) => {
- const song1 = {
- songId: 1,
- title: 'Song1',
- storeLinks: [],
- artists: [
- {
- artistId: 1,
- name: 'Artist1',
- storeLinks: [],
- }
- ],
- tags: [],
- albums: []
- };
- const song2 = {
- songId: 2,
- title: 'Song2',
- storeLinks: [],
- artists: [
- {
- artistId: 1,
- name: 'Artist1',
- storeLinks: [],
- }
- ],
- tags: [],
- albums: []
- };
- const song3 = {
- songId: 3,
- title: 'Song3',
- storeLinks: [],
- artists: [
- {
- artistId: 2,
- name: 'Artist2',
- storeLinks: [],
- }
- ],
- tags: [],
- albums: []
- };
-
- async function checkAllSongs(req) {
- await req
- .post('/query')
- .send({
- "query": {},
- 'offsetsLimits': {
- 'songOffset': 0,
- 'songLimit': 10,
+ async function checkIdIn(req) {
+ await req
+ .post('/query')
+ .send({
+ "query": {
+ "prop": "songId",
+ "propOperator": "IN",
+ "propOperand": [1, 3, 5]
+ },
+ 'offsetsLimits': {
+ 'songOffset': 0,
+ 'songLimit': 10,
+ },
+ 'ordering': {
+ 'orderBy': {
+ 'type': 0,
},
- 'ordering': {
- 'orderBy': {
- 'type': 0,
- },
- 'ascending': true
- }
- })
- .then((res) => {
- expect(res).to.have.status(200);
- expect(res.body).to.deep.equal({
- songs: [ song1, song2, song3 ],
- artists: [],
- tags: [],
- albums: [],
- });
+ 'ascending': true
+ }
+ })
+ .then((res) => {
+ expect(res).to.have.status(200);
+ expect(res.body).to.deep.equal({
+ songs: [song1, song3],
+ artists: [],
+ tags: [],
+ albums: [],
});
- }
+ });
+ }
- async function checkIdIn(req) {
- await req
- .post('/query')
- .send({
- "query": {
- "prop": "songId",
- "propOperator": "IN",
- "propOperand": [1, 3, 5]
- },
- 'offsetsLimits': {
- 'songOffset': 0,
- 'songLimit': 10,
+ async function checkIdNotIn(req) {
+ await req
+ .post('/query')
+ .send({
+ "query": {
+ "prop": "songId",
+ "propOperator": "NOTIN",
+ "propOperand": [1, 3, 5]
+ },
+ 'offsetsLimits': {
+ 'songOffset': 0,
+ 'songLimit': 10,
+ },
+ 'ordering': {
+ 'orderBy': {
+ 'type': 0,
},
- 'ordering': {
- 'orderBy': {
- 'type': 0,
- },
- 'ascending': true
- }
- })
- .then((res) => {
- expect(res).to.have.status(200);
- expect(res.body).to.deep.equal({
- songs: [ song1, song3 ],
- artists: [],
- tags: [],
- albums: [],
- });
+ 'ascending': true
+ }
+ })
+ .then((res) => {
+ expect(res).to.have.status(200);
+ expect(res.body).to.deep.equal({
+ songs: [song2],
+ artists: [],
+ tags: [],
+ albums: [],
});
- }
+ });
+ }
- async function checkIdNotIn(req) {
- await req
- .post('/query')
- .send({
- "query": {
- "prop": "songId",
- "propOperator": "NOTIN",
- "propOperand": [1, 3, 5]
- },
- 'offsetsLimits': {
- 'songOffset': 0,
- 'songLimit': 10,
+ async function checkArtistIdIn(req) {
+ await req
+ .post('/query')
+ .send({
+ "query": {
+ "prop": "artistId",
+ "propOperator": "IN",
+ "propOperand": [1]
+ },
+ 'offsetsLimits': {
+ 'songOffset': 0,
+ 'songLimit': 10,
+ },
+ 'ordering': {
+ 'orderBy': {
+ 'type': 0,
},
- 'ordering': {
- 'orderBy': {
- 'type': 0,
- },
- 'ascending': true
- }
- })
- .then((res) => {
- expect(res).to.have.status(200);
- expect(res.body).to.deep.equal({
- songs: [ song2 ],
- artists: [],
- tags: [],
- albums: [],
- });
+ 'ascending': true
+ }
+ })
+ .then((res) => {
+ expect(res).to.have.status(200);
+ expect(res.body).to.deep.equal({
+ songs: [song1, song2],
+ artists: [],
+ tags: [],
+ albums: [],
});
- }
+ });
+ }
- async function checkArtistIdIn(req) {
- await req
- .post('/query')
- .send({
- "query": {
- "prop": "artistId",
- "propOperator": "IN",
- "propOperand": [1]
- },
- 'offsetsLimits': {
- 'songOffset': 0,
- 'songLimit': 10,
- },
- 'ordering': {
- 'orderBy': {
- 'type': 0,
+ async function checkOrRelation(req) {
+ await req
+ .post('/query')
+ .send({
+ "query": {
+ "childrenOperator": "OR",
+ "children": [
+ {
+ "prop": "artistId",
+ "propOperator": "IN",
+ "propOperand": [2]
},
- 'ascending': true
- }
- })
- .then((res) => {
- expect(res).to.have.status(200);
- expect(res.body).to.deep.equal({
- songs: [ song1, song2 ],
- artists: [],
- tags: [],
- albums: [],
- });
- });
- }
-
- async function checkOrRelation(req) {
- await req
- .post('/query')
- .send({
- "query": {
- "childrenOperator": "OR",
- "children": [
- {
- "prop": "artistId",
- "propOperator": "IN",
- "propOperand": [2]
- },
- {
- "prop": "songId",
- "propOperator": "EQ",
- "propOperand": 1
- }
- ]
- },
- 'offsetsLimits': {
- 'songOffset': 0,
- 'songLimit': 10,
+ {
+ "prop": "songId",
+ "propOperator": "EQ",
+ "propOperand": 1
+ }
+ ]
+ },
+ 'offsetsLimits': {
+ 'songOffset': 0,
+ 'songLimit': 10,
+ },
+ 'ordering': {
+ 'orderBy': {
+ 'type': 0,
},
- 'ordering': {
- 'orderBy': {
- 'type': 0,
- },
- 'ascending': true
- }
- })
- .then((res) => {
- expect(res).to.have.status(200);
- expect(res.body).to.deep.equal({
- songs: [ song1, song3 ],
- artists: [],
- tags: [],
- albums: [],
- });
+ 'ascending': true
+ }
+ })
+ .then((res) => {
+ expect(res).to.have.status(200);
+ expect(res.body).to.deep.equal({
+ songs: [song1, song3],
+ artists: [],
+ tags: [],
+ albums: [],
});
- }
-
- var req = chai.request(app).keepOpen();
+ });
+ }
- helpers.createArtist(req, { name: "Artist1" }, 200)
- .then(() => helpers.createArtist(req, { name: "Artist2" }, 200))
- .then(() => helpers.createSong(req, { title: "Song1", artistIds: [1] }, 200))
- .then(() => helpers.createSong(req, { title: "Song2", artistIds: [1] }, 200))
- .then(() => helpers.createSong(req, { title: "Song3", artistIds: [2] }, 200))
- .then(() => checkAllSongs(req))
- .then(() => checkIdIn(req))
- .then(() => checkIdNotIn(req))
- .then(() => checkArtistIdIn(req))
- .then(() => checkOrRelation(req))
- .then(req.close)
- .then(done)
- })
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createArtist(req, { name: "Artist1" }, 200);
+ await helpers.createArtist(req, { name: "Artist2" }, 200);
+ await helpers.createSong(req, { title: "Song1", artistIds: [1] }, 200);
+ await helpers.createSong(req, { title: "Song2", artistIds: [1] }, 200);
+ await helpers.createSong(req, { title: "Song3", artistIds: [2] }, 200);
+ await checkAllSongs(req);
+ await checkIdIn(req);
+ await checkIdNotIn(req);
+ await checkArtistIdIn(req);
+ await checkOrRelation(req);
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
\ No newline at end of file
diff --git a/server/test/integration/flows/SongFlow.js b/server/test/integration/flows/SongFlow.js
index 9bd5234..00b7a0b 100644
--- a/server/test/integration/flows/SongFlow.js
+++ b/server/test/integration/flows/SongFlow.js
@@ -4,114 +4,128 @@ const express = require('express');
import { SetupApp } from '../../../app';
import { expect } from 'chai';
import * as helpers from './helpers';
+import { sha512 } from 'js-sha512';
async function init() {
chai.use(chaiHttp);
const app = express();
- SetupApp(app, await helpers.initTestDB(), '');
- return app;
+ const knex = await helpers.initTestDB();
+
+ // Add test users.
+ await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users');
+ await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users');
+
+ SetupApp(app, knex, '');
+
+ // Login as a test user.
+ var agent = chai.request.agent(app);
+ await agent
+ .post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1'))
+ .send({});
+ return agent;
}
describe('POST /song with no title', () => {
- it('should fail', done => {
- init().then((app) => {
- chai
- .request(app)
- .post('/song')
- .send({})
- .then((res) => {
- expect(res).to.have.status(400);
- done();
- });
- })
+ it('should fail', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createSong(req, {}, 400);
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /song with only a title', () => {
- it('should return the first available id', done => {
- init().then(async(app) => {
- chai
- .request(app)
- .post('/song')
- .send({
- title: "MySong"
- })
- .then((res) => {
- expect(res).to.have.status(200);
- expect(res.body).to.deep.equal({
- id: 1
- });
- done();
- });
- })
+ it('should return the first available id', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createSong(req, { title: "MySong" }, 200, { id: 1 });
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /song with a nonexistent artist Id', () => {
- it('should fail', done => {
- init().then(async (app) => {
- chai
- .request(app)
- .post('/song')
- .send({
- title: "MySong",
- artistIds: [1]
- })
- .then((res) => {
- expect(res).to.have.status(400);
- done();
- });
- })
+ it('should fail', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createSong(req, { title: "MySong", artistIds: [1] }, 400);
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /song with an existing artist Id', () => {
- it('should succeed', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createArtist(req, { name: "MyArtist" }, 200, { id: 1 })
- .then(() => helpers.createSong(req, { title: "MySong", artistIds: [ 1 ] }, 200, { id: 1 }) )
- .then(req.close)
- .then(done);
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createArtist(req, { name: "MyArtist" }, 200, { id: 1 });
+ await helpers.createSong(req, { title: "MySong", artistIds: [1] }, 200, { id: 1 });
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /song with two existing artist Ids', () => {
- it('should succeed', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1 })
- .then(() => helpers.createArtist(req, { name: "Artist2" }, 200, { id: 2 }) )
- .then(() => helpers.createSong(req, { title: "MySong", artistIds: [1, 2] }, 200, { id: 1 }) )
- .then(req.close)
- .then(done);
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1 })
+ await helpers.createArtist(req, { name: "Artist2" }, 200, { id: 2 })
+ await helpers.createSong(req, { title: "MySong", artistIds: [1, 2] }, 200, { id: 1 })
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /song with an existent and a nonexistent artist Id', () => {
- it('should fail', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1 })
- .then(() => helpers.createSong(req, { title: "MySong", artistIds: [1, 2] }, 400) )
- .then(req.close)
- .then(done);
- });
+ it('should fail', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1 })
+ await helpers.createSong(req, { title: "MySong", artistIds: [1, 2] }, 400)
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /song with tags', () => {
- it('should succeed', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createTag(req, { name: "Root" }, 200, { id: 1 })
- .then(() => helpers.createTag(req, { name: "Leaf", parentId: 1 }, 200, { id: 2 }))
- .then(() => helpers.createSong(req, { title: "Song", tagIds: [ 1, 2 ] }, 200, { id: 1 }))
- .then(() => helpers.checkSong(req, 1, 200, { title: "Song", storeLinks: [], tagIds: [ 1, 2 ], albumIds: [], artistIds: [] }))
- .then(req.close)
- .then(done);
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createTag(req, { name: "Root" }, 200, { id: 1 })
+ await helpers.createTag(req, { name: "Leaf", parentId: 1 }, 200, { id: 2 })
+ await helpers.createSong(req, { title: "Song", tagIds: [1, 2] }, 200, { id: 1 })
+ await helpers.checkSong(req, 1, 200, { title: "Song", storeLinks: [], tagIds: [1, 2], albumIds: [], artistIds: [] })
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
diff --git a/server/test/integration/flows/TagFlow.js b/server/test/integration/flows/TagFlow.js
index 7209f8b..c0f1b29 100644
--- a/server/test/integration/flows/TagFlow.js
+++ b/server/test/integration/flows/TagFlow.js
@@ -4,72 +4,84 @@ const express = require('express');
import { SetupApp } from '../../../app';
import { expect } from 'chai';
import * as helpers from './helpers';
+import { sha512 } from 'js-sha512';
async function init() {
chai.use(chaiHttp);
const app = express();
- SetupApp(app, await helpers.initTestDB(), '');
- return app;
+ const knex = await helpers.initTestDB();
+
+ // Add test users.
+ await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users');
+ await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users');
+
+ SetupApp(app, knex, '');
+
+ // Login as a test user.
+ var agent = chai.request.agent(app);
+ await agent
+ .post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1'))
+ .send({});
+ return agent;
}
describe('POST /tag with no name', () => {
- it('should fail', done => {
- init().then((app) => {
- chai
- .request(app)
- .post('/tag')
- .send({})
- .then((res) => {
- expect(res).to.have.status(400);
- done();
- });
- });
+ it('should fail', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createTag(req, {}, 400);
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /tag with a correct request', () => {
- it('should succeed', done => {
- init().then((app) => {
- chai
- .request(app)
- .post('/tag')
- .send({
- name: "MyTag"
- })
- .then((res) => {
- expect(res).to.have.status(200);
- expect(res.body).to.deep.equal({
- id: 1
- });
- done();
- });
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createTag(req, { name: "MyTag" }, 200, { id: 1 });
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('POST /tag with a parent', () => {
- it('should succeed', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 })
- .then(() => helpers.createTag(req, { name: "Tag2", parentId: 1 }, 200, { id: 2 }) )
- .then(() => helpers.checkTag(req, 2, 200, { name: "Tag2", parentId: 1 }))
- .then(req.close)
- .then(done);
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 })
+ await helpers.createTag(req, { name: "Tag2", parentId: 1 }, 200, { id: 2 })
+ await helpers.checkTag(req, 2, 200, { name: "Tag2", parentId: 1 })
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
describe('PUT /tag with a new parent', () => {
- it('should succeed', done => {
- init().then((app) => {
- var req = chai.request(app).keepOpen();
- helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 })
- .then(() => helpers.createTag(req, { name: "Tag2" }, 200, { id: 2 }) )
- .then(() => helpers.modifyTag(req, 2, { parentId: 1 }, 200) )
- .then(() => helpers.checkTag(req, 2, 200, { name: "Tag2", parentId: 1 }))
- .then(req.close)
- .then(done);
- });
+ it('should succeed', async done => {
+ let agent = await init();
+ let req = agent.keepOpen();
+ try {
+ await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 })
+ await helpers.createTag(req, { name: "Tag2" }, 200, { id: 2 })
+ await helpers.modifyTag(req, 2, { parentId: 1 }, 200)
+ await helpers.checkTag(req, 2, 200, { name: "Tag2", parentId: 1 })
+ } finally {
+ req.close();
+ agent.close();
+ done();
+ }
});
});
\ No newline at end of file
diff --git a/server/test/integration/flows/helpers.js b/server/test/integration/flows/helpers.js
index 6d2da62..bbf427e 100644
--- a/server/test/integration/flows/helpers.js
+++ b/server/test/integration/flows/helpers.js
@@ -1,4 +1,5 @@
import { expect } from "chai";
+import { sha512 } from "js-sha512";
export async function initTestDB() {
// Allow different database configs - but fall back to SQLite in memory if necessary.
@@ -11,6 +12,7 @@ export async function initTestDB() {
// Undoing and doing the migrations is a test in itself.
await knex.migrate.rollback(undefined, true);
await knex.migrate.latest();
+
return knex;
}
@@ -184,4 +186,47 @@ export async function checkAlbum(
expectStatus && expect(res).to.have.status(expectStatus);
expectResponse && expect(res.body).to.deep.equal(expectResponse);
})
+}
+
+export async function createUser(
+ req,
+ email,
+ password,
+ expectStatus = undefined,
+ expectResponse = undefined,
+) {
+ const res = await req
+ .post('/register')
+ .send({
+ email: email,
+ password: password,
+ });
+ expectStatus && expect(res).to.have.status(expectStatus);
+ expectResponse && expect(res.body).to.deep.equal(expectResponse);
+}
+
+export async function login(
+ req,
+ email,
+ password,
+ expectStatus = undefined,
+ expectResponse = undefined,
+) {
+ const res = await req
+ .post('/login?username=' + encodeURIComponent(email) + '&password=' + encodeURIComponent(password))
+ .send({});
+ expectStatus && expect(res).to.have.status(expectStatus);
+ expectResponse && expect(res.body).to.deep.equal(expectResponse);
+}
+
+export async function logout(
+ req,
+ expectStatus = undefined,
+ expectResponse = undefined,
+) {
+ const res = await req
+ .post('/logout')
+ .send({});
+ expectStatus && expect(res).to.have.status(expectStatus);
+ expectResponse && expect(res.body).to.deep.equal(expectResponse);
}
\ No newline at end of file