1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-12-25 02:04:15 +02:00

implementing sharing

This commit is contained in:
Braun Patrik 2017-07-03 19:17:49 +02:00
parent b499f0b5ac
commit eaa3c15825
67 changed files with 1820 additions and 1085 deletions

View File

@ -1,11 +1,11 @@
# PiGallery2
[![npm version](https://badge.fury.io/js/pigallery2.svg)](https://badge.fury.io/js/pigallery2)
[![Build Status](https://travis-ci.org/bpatrik/PiGallery2.svg?branch=master)](https://travis-ci.org/bpatrik/PiGallery2)
[![Coverage Status](https://coveralls.io/repos/github/bpatrik/PiGallery2/badge.svg?branch=master)](https://coveralls.io/github/bpatrik/PiGallery2?branch=master)
[![Build Status](https://travis-ci.org/bpatrik/pigallery2.svg?branch=master)](https://travis-ci.org/bpatrik/pigallery2)
[![Coverage Status](https://coveralls.io/repos/github/bpatrik/pigallery2/badge.svg?branch=master)](https://coveralls.io/github/bpatrik/pigallery2?branch=master)
[![Heroku](https://heroku-badge.herokuapp.com/?app=pigallery2&style=flat)](https://pigallery2.herokuapp.com)
[![Code Climate](https://codeclimate.com/github/bpatrik/PiGallery2/badges/gpa.svg)](https://codeclimate.com/github/bpatrik/PiGallery2)
[![Dependency Status](https://david-dm.org/bpatrik/PiGallery2.svg)](https://david-dm.org/bpatrik/PiGallery2)
[![devDependency Status](https://david-dm.org/bpatrik/PiGallery2/dev-status.svg)](https://david-dm.org/bpatrik/PiGallery2#info=devDependencies)
[![Code Climate](https://codeclimate.com/github/bpatrik/pigallery2/badges/gpa.svg)](https://codeclimate.com/github/bpatrik/pigallery2)
[![Dependency Status](https://david-dm.org/bpatrik/pigallery2.svg)](https://david-dm.org/bpatrik/pigallery2)
[![devDependency Status](https://david-dm.org/bpatrik/pigallery2/dev-status.svg)](https://david-dm.org/bpatrik/pigallery2#info=devDependencies)
This is a directory-first photo gallery website, optimised for running on low resource servers (especially on raspberry pi)
@ -36,7 +36,7 @@ Full node install description: https://raspberrypi.stackexchange.com/questions/4
### Install PiGallery2
```bash
cd ~
wget https://github.com/bpatrik/PiGallery2/archive/1.0.0-beta.0.tar.gz
wget https://github.com/bpatrik/pigallery2/archive/1.0.0-beta.0.tar.gz
tar -xzvf 1.0.0-beta.0.tar.gz
cd PiGallery2-1.0.0-beta.0
npm install

View File

@ -5,6 +5,7 @@ class ProjectPathClass {
public Root: string;
public ImageFolder: string;
public ThumbnailFolder: string;
public FrontendFolder: string;
isAbsolutePath(pathStr: string) {
return path.resolve(pathStr) === path.normalize(pathStr);
@ -18,6 +19,7 @@ class ProjectPathClass {
this.Root = path.join(__dirname, "/../");
this.ImageFolder = this.isAbsolutePath(Config.Server.imagesFolder) ? Config.Server.imagesFolder : path.join(this.Root, Config.Server.imagesFolder);
this.ThumbnailFolder = this.isAbsolutePath(Config.Server.thumbnail.folder) ? Config.Server.thumbnail.folder : path.join(this.Root, Config.Server.thumbnail.folder);
this.FrontendFolder = path.join(this.Root, 'dist')
}
}

View File

@ -11,13 +11,14 @@ import {PhotoDTO} from "../../common/entities/PhotoDTO";
import {ProjectPath} from "../ProjectPath";
import {Logger} from "../Logger";
import {Config} from "../../common/config/private/Config";
import {UserUtil} from "../../common/entities/UserDTO";
const LOG_TAG = "[GalleryMWs]";
export class GalleryMWs {
public static listDirectory(req: Request, res: Response, next: NextFunction) {
public static async listDirectory(req: Request, res: Response, next: NextFunction) {
let directoryName = req.params.directory || "/";
let absoluteDirectoryName = path.join(ProjectPath.ImageFolder, directoryName);
@ -25,17 +26,23 @@ export class GalleryMWs {
return next();
}
ObjectManagerRepository.getInstance().getGalleryManager().listDirectory(directoryName, (err, directory: DirectoryDTO) => {
if (err || !directory) {
Logger.warn(LOG_TAG, "Error during listing the directory", err);
console.error(err);
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
try {
const directory = await ObjectManagerRepository.getInstance().GalleryManager.listDirectory(directoryName);
if (req.session.user.permissions &&
req.session.user.permissions.length > 0 &&
req.session.user.permissions[0] != "/") {
directory.directories = directory.directories.filter(d =>
UserUtil.isDirectoryAvailable(d, req.session.user.permissions));
}
req.resultPipe = new ContentWrapper(directory, null);
return next();
});
} catch (err) {
Logger.warn(LOG_TAG, "Error during listing the directory", err);
console.error(err);
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
}
@ -99,7 +106,7 @@ export class GalleryMWs {
type = parseInt(req.query.type);
}
ObjectManagerRepository.getInstance().getSearchManager().search(req.params.text, type, (err, result: SearchResultDTO) => {
ObjectManagerRepository.getInstance().SearchManager.search(req.params.text, type, (err, result: SearchResultDTO) => {
if (err || !result) {
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
@ -119,7 +126,7 @@ export class GalleryMWs {
}
ObjectManagerRepository.getInstance().getSearchManager().instantSearch(req.params.text, (err, result: SearchResultDTO) => {
ObjectManagerRepository.getInstance().SearchManager.instantSearch(req.params.text, (err, result: SearchResultDTO) => {
if (err || !result) {
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
@ -136,7 +143,7 @@ export class GalleryMWs {
return next();
}
ObjectManagerRepository.getInstance().getSearchManager().autocomplete(req.params.text, (err, items: Array<AutoCompleteItem>) => {
ObjectManagerRepository.getInstance().SearchManager.autocomplete(req.params.text, (err, items: Array<AutoCompleteItem>) => {
if (err || !items) {
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}

View File

@ -2,6 +2,7 @@ import {NextFunction, Request, Response} from "express";
import {Error, ErrorCodes} from "../../common/entities/Error";
import {Utils} from "../../common/Utils";
import {Message} from "../../common/entities/Message";
import {SharingDTO} from "../../common/entities/SharingDTO";
export class RenderingMWs {
@ -15,14 +16,23 @@ export class RenderingMWs {
public static renderSessionUser(req: Request, res: Response, next: NextFunction) {
if (!(req.session.user)) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
return next(new Error(ErrorCodes.GENERAL_ERROR, "User not exists"));
}
let user = Utils.clone(req.session.user);
const user = Utils.clone(req.session.user);
delete user.password;
RenderingMWs.renderMessage(res, user);
}
public static renderSharing(req: Request, res: Response, next: NextFunction) {
if (!req.resultPipe)
return next();
const sharing = Utils.clone<SharingDTO>(req.resultPipe);
delete sharing.password;
RenderingMWs.renderMessage(res, sharing);
}
public static renderFile(req: Request, res: Response, next: NextFunction) {
if (!req.resultPipe)
return next();

View File

@ -0,0 +1,109 @@
import {NextFunction, Request, Response} from "express";
import {CreateSharingDTO, SharingDTO} from "../../common/entities/SharingDTO";
import {ObjectManagerRepository} from "../model/ObjectManagerRepository";
import {Logger} from "../Logger";
import {Error, ErrorCodes} from "../../common/entities/Error";
const LOG_TAG = "[SharingMWs]";
export class SharingMWs {
private static generateKey(): string {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4();
}
public static async getSharing(req: Request, res: Response, next: NextFunction) {
const sharingKey = req.params.sharingKey;
try {
req.resultPipe = await ObjectManagerRepository.getInstance().SharingManager.findOne({sharingKey: sharingKey});
return next();
} catch (err) {
Logger.warn(LOG_TAG, "Error during retrieving sharing link", err);
console.error(err);
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
}
public static async createSharing(req: Request, res: Response, next: NextFunction) {
if ((typeof req.body === 'undefined') || (typeof req.body.createSharing === 'undefined')) {
return next(new Error(ErrorCodes.INPUT_ERROR, "createSharing filed is missing"));
}
const createSharing: CreateSharingDTO = req.body.createSharing;
let sharingKey = SharingMWs.generateKey();
//create one not yet used
while (true) {
try {
await ObjectManagerRepository.getInstance().SharingManager.findOne({sharingKey: sharingKey});
sharingKey = this.generateKey();
} catch (err) {
break;
}
}
const directoryName = req.params.directory || "/";
let sharing: SharingDTO = {
id: null,
sharingKey: sharingKey,
path: directoryName,
password: createSharing.password,
creator: req.session.user,
expires: Date.now() + createSharing.valid,
includeSubfolders: createSharing.includeSubfolders,
timeStamp: Date.now()
};
try {
req.resultPipe = await ObjectManagerRepository.getInstance().SharingManager.createSharing(sharing);
return next();
} catch (err) {
Logger.warn(LOG_TAG, "Error during creating sharing link", err);
console.error(err);
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
}
public static async updateSharing(req: Request, res: Response, next: NextFunction) {
if ((typeof req.body === 'undefined') || (typeof req.body.updateSharing === 'undefined')) {
return next(new Error(ErrorCodes.INPUT_ERROR, "updateSharing filed is missing"));
}
const updateSharing: CreateSharingDTO = req.body.updateSharing;
const directoryName = req.params.directory || "/";
let sharing: SharingDTO = {
id: updateSharing.id,
path: directoryName,
sharingKey: "",
password: updateSharing.password,
creator: req.session.user,
expires: Date.now() + updateSharing.valid,
includeSubfolders: updateSharing.includeSubfolders,
timeStamp: Date.now()
};
try {
req.resultPipe = await ObjectManagerRepository.getInstance().SharingManager.updateSharing(sharing);
return next();
} catch (err) {
Logger.warn(LOG_TAG, "Error during creating sharing link", err);
console.error(err);
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
}
}

View File

@ -1,5 +1,5 @@
import {Metadata, SharpInstance} from "@types/sharp";
import {Dimensions, State} from "@types/gm";
import {Metadata, SharpInstance} from "sharp";
import {Dimensions, State} from "gm";
import {Logger} from "../../Logger";
import {Error, ErrorCodes} from "../../../common/entities/Error";
@ -15,7 +15,6 @@ export module ThumbnailRenderers {
}
export const jimp = (input: RendererInput, done) => {
//generate thumbnail
const Jimp = require("jimp");
Jimp.read(input.imagePath).then((image) => {
@ -53,7 +52,6 @@ export module ThumbnailRenderers {
};
export const sharp = (input: RendererInput, done) => {
//generate thumbnail
const sharp = require("sharp");
@ -99,11 +97,13 @@ export module ThumbnailRenderers {
}).catch(function (err) {
// const Error = require(input.__dirname + "/../../../common/entities/Error").Error;
// const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes;
console.error(err);
return done(new Error(ErrorCodes.GENERAL_ERROR, err));
});
} catch (err) {
// const Error = require(input.__dirname + "/../../../common/entities/Error").Error;
// const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes;
console.error(err);
return done(new Error(ErrorCodes.GENERAL_ERROR, err));
}
});
@ -112,7 +112,6 @@ export module ThumbnailRenderers {
export const gm = (input: RendererInput, done) => {
//generate thumbnail
const gm = require("gm");

View File

@ -139,7 +139,7 @@ export class ThumbnailGeneratorMWs {
private static generateImage(imagePath: string, size: number, makeSquare: boolean, req: Request, res: Response, next: NextFunction) {
ThumbnailGeneratorMWs.init();
//generate thumbnail path
let thPath = path.join(ProjectPath.ThumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(imagePath, size));
@ -189,4 +189,3 @@ export class ThumbnailGeneratorMWs {
}
}
ThumbnailGeneratorMWs.init();

View File

@ -1,67 +1,124 @@
///<reference path="../customtypings/ExtendedRequest.d.ts"/>
import {NextFunction, Request, Response} from "express";
import {Error, ErrorCodes} from "../../../common/entities/Error";
import {UserDTO, UserRoles} from "../../../common/entities/UserDTO";
import {UserDTO, UserRoles, UserUtil} from "../../../common/entities/UserDTO";
import {ObjectManagerRepository} from "../../model/ObjectManagerRepository";
import {Config} from "../../../common/config/private/Config";
export class AuthenticationMWs {
public static authenticate(req: Request, res: Response, next: NextFunction) {
if (Config.Client.authenticationRequired === false) {
req.session.user = <UserDTO>{name: "", role: UserRoles.Admin};
private static async getSharingUser(req: Request) {
if (Config.Client.Sharing.enabled === true &&
Config.Client.Sharing.passwordProtected === false &&
(!!req.query.sk || !!req.params.sharingKey)) {
const sharing = await ObjectManagerRepository.getInstance().SharingManager.findOne({
sharingKey: req.query.sk || req.params.sharingKey,
});
if (!sharing) {
return null;
}
return next();
}
if (typeof req.session.user === 'undefined') {
return next(new Error(ErrorCodes.NOT_AUTHENTICATED));
}
let path = sharing.path;
if (sharing.includeSubfolders == true) {
path += "*";
}
return <UserDTO>{name: "Guest", role: UserRoles.Guest, permissions: [path]};
}
return null;
}
public static async authenticate(req: Request, res: Response, next: NextFunction) {
if (Config.Client.authenticationRequired === false) {
req.session.user = <UserDTO>{name: "", role: UserRoles.Admin};
return next();
}
try {
const user = await AuthenticationMWs.getSharingUser(req);
if (!!user) {
req.session.user = user;
return next();
}
} catch (err) {
console.error(err);
return next(new Error(ErrorCodes.CREDENTIAL_NOT_FOUND));
}
if (typeof req.session.user === 'undefined') {
return next(new Error(ErrorCodes.NOT_AUTHENTICATED));
}
return next();
}
public static authorise(role: UserRoles) {
return (req: Request, res: Response, next: NextFunction) => {
if (req.session.user.role < role) {
return next(new Error(ErrorCodes.NOT_AUTHORISED));
}
return next();
};
}
public static authoriseDirectory(req: Request, res: Response, next: NextFunction) {
if (req.session.user.permissions == null ||
req.session.user.permissions.length == 0 ||
req.session.user.permissions[0] == "/") {
return next();
}
public static authorise(role: UserRoles) {
return (req: Request, res: Response, next: NextFunction) => {
if (req.session.user.role < role) {
return next(new Error(ErrorCodes.NOT_AUTHORISED));
}
return next();
};
}
const directoryName = req.params.directory || "/";
if (UserUtil.isPathAvailable(directoryName, req.session.user.permissions) == true) {
return next();
public static inverseAuthenticate(req: Request, res: Response, next: NextFunction) {
if (typeof req.session.user !== 'undefined') {
return next(new Error(ErrorCodes.ALREADY_AUTHENTICATED));
}
return next(new Error(ErrorCodes.PERMISSION_DENIED));
}
public static inverseAuthenticate(req: Request, res: Response, next: NextFunction) {
if (typeof req.session.user !== 'undefined') {
return next(new Error(ErrorCodes.ALREADY_AUTHENTICATED));
}
return next();
}
public static async login(req: Request, res: Response, next: NextFunction) {
//not enough parameter
if ((typeof req.body === 'undefined') || (typeof req.body.loginCredential === 'undefined') || (typeof req.body.loginCredential.username === 'undefined') ||
(typeof req.body.loginCredential.password === 'undefined')) {
return next(new Error(ErrorCodes.INPUT_ERROR));
}
try {
//lets find the user
req.session.user = await ObjectManagerRepository.getInstance().UserManager.findOne({
name: req.body.loginCredential.username,
password: req.body.loginCredential.password
});
return next();
} catch (err) {
//if its a shared link, login as guest
try {
const user = await AuthenticationMWs.getSharingUser(req);
if (user) {
req.session.user = user;
return next();
}
return next();
} catch (err) {
console.error(err);
return next(new Error(ErrorCodes.CREDENTIAL_NOT_FOUND));
}
console.error(err);
return next(new Error(ErrorCodes.CREDENTIAL_NOT_FOUND));
}
public static login(req: Request, res: Response, next: NextFunction) {
//not enough parameter
if ((typeof req.body === 'undefined') || (typeof req.body.loginCredential === 'undefined') || (typeof req.body.loginCredential.username === 'undefined') ||
(typeof req.body.loginCredential.password === 'undefined')) {
return next();
}
//lets find the user
ObjectManagerRepository.getInstance().getUserManager().findOne({
name: req.body.loginCredential.username,
password: req.body.loginCredential.password
}, (err, result) => {
if ((err) || (!result)) {
console.error(err);
return next(new Error(ErrorCodes.CREDENTIAL_NOT_FOUND));
}
}
public static logout(req: Request, res: Response, next: NextFunction) {
delete req.session.user;
return next();
}
req.session.user = result;
return next();
});
}
public static logout(req: Request, res: Response, next: NextFunction) {
delete req.session.user;
return next();
}
}
}

View File

@ -1,106 +1,107 @@
import {NextFunction, Request, Response} from "express";
import {Error, ErrorCodes} from "../../../common/entities/Error";
import {ObjectManagerRepository} from "../../model/ObjectManagerRepository";
import {UserDTO} from "../../../common/entities/UserDTO";
import {Utils} from "../../../common/Utils";
import {Config} from "../../../common/config/private/Config";
export class UserMWs {
public static changePassword(req:Request, res:Response, next:NextFunction) {
if (Config.Client.authenticationRequired === false) {
return next(new Error(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
if ((typeof req.body === 'undefined') || (typeof req.body.userModReq === 'undefined')
|| (typeof req.body.userModReq.id === 'undefined')
|| (typeof req.body.userModReq.oldPassword === 'undefined')
|| (typeof req.body.userModReq.newPassword === 'undefined')) {
return next();
}
public static async changePassword(req: Request, res: Response, next: NextFunction) {
if (Config.Client.authenticationRequired === false) {
return next(new Error(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
if ((typeof req.body === 'undefined') || (typeof req.body.userModReq === 'undefined')
|| (typeof req.body.userModReq.id === 'undefined')
|| (typeof req.body.userModReq.oldPassword === 'undefined')
|| (typeof req.body.userModReq.newPassword === 'undefined')) {
return next();
}
ObjectManagerRepository.getInstance().getUserManager().changePassword(req.body.userModReq, (err, result) => {
if ((err) || (!result)) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
try {
await ObjectManagerRepository.getInstance().UserManager.changePassword(req.body.userModReq);
return next();
return next();
});
} catch (err) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
}
public static async createUser(req: Request, res: Response, next: NextFunction) {
if (Config.Client.authenticationRequired === false) {
return next(new Error(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
if ((typeof req.body === 'undefined') || (typeof req.body.newUser === 'undefined')) {
return next();
}
try {
await ObjectManagerRepository.getInstance().UserManager.createUser(req.body.newUser);
return next();
} catch (err) {
return next(new Error(ErrorCodes.USER_CREATION_ERROR));
}
public static createUser(req:Request, res:Response, next:NextFunction) {
if (Config.Client.authenticationRequired === false) {
return next(new Error(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
if ((typeof req.body === 'undefined') || (typeof req.body.newUser === 'undefined')) {
return next();
}
ObjectManagerRepository.getInstance().getUserManager().createUser(req.body.newUser, (err, result) => {
if ((err) || (!result)) {
return next(new Error(ErrorCodes.USER_CREATION_ERROR));
}
return next();
});
}
public static async deleteUser(req: Request, res: Response, next: NextFunction) {
if (Config.Client.authenticationRequired === false) {
return next(new Error(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
public static deleteUser(req:Request, res:Response, next:NextFunction) {
if (Config.Client.authenticationRequired === false) {
return next(new Error(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}
ObjectManagerRepository.getInstance().getUserManager().deleteUser(req.params.id, (err, result) => {
if ((err) || (!result)) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
return next();
});
}
public static changeRole(req:Request, res:Response, next:NextFunction) {
if (Config.Client.authenticationRequired === false) {
return next(new Error(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')
|| (typeof req.body === 'undefined') || (typeof req.body.newRole === 'undefined')) {
return next();
}
ObjectManagerRepository.getInstance().getUserManager().changeRole(req.params.id, req.body.newRole, (err) => {
if (err) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
return next();
});
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}
public static listUsers(req:Request, res:Response, next:NextFunction) {
if (Config.Client.authenticationRequired === false) {
return next(new Error(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
ObjectManagerRepository.getInstance().getUserManager().find({}, (err, result: Array<UserDTO>) => {
if ((err) || (!result)) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
result = Utils.clone(result);
for (let i = 0; i < result.length; i++) {
result[i].password = "";
}
try {
await ObjectManagerRepository.getInstance().UserManager.deleteUser(req.params.id);
return next();
req.resultPipe = result;
return next();
});
} catch (err) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
}
}
public static async changeRole(req: Request, res: Response, next: NextFunction) {
if (Config.Client.authenticationRequired === false) {
return next(new Error(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')
|| (typeof req.body === 'undefined') || (typeof req.body.newRole === 'undefined')) {
return next();
}
try {
await ObjectManagerRepository.getInstance().UserManager.changeRole(req.params.id, req.body.newRole);
return next();
} catch (err) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
}
public static async listUsers(req: Request, res: Response, next: NextFunction) {
if (Config.Client.authenticationRequired === false) {
return next(new Error(ErrorCodes.USER_MANAGEMENT_DISABLED));
}
try {
let result = await ObjectManagerRepository.getInstance().UserManager.find({});
result = Utils.clone(result);
for (let i = 0; i < result.length; i++) {
result[i].password = "";
}
req.resultPipe = result;
} catch (err) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
}
}

View File

@ -5,51 +5,52 @@ import {ObjectManagerRepository} from "../../model/ObjectManagerRepository";
export class UserRequestConstrainsMWs {
public static forceSelfRequest(req:Request, res:Response, next:NextFunction) {
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}
if (req.session.user.id !== req.params.id) {
return next(new Error(ErrorCodes.NOT_AUTHORISED));
}
return next();
public static forceSelfRequest(req: Request, res: Response, next: NextFunction) {
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}
if (req.session.user.id !== req.params.id) {
return next(new Error(ErrorCodes.NOT_AUTHORISED));
}
return next();
}
public static notSelfRequest(req:Request, res:Response, next:NextFunction) {
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}
if (req.session.user.id === req.params.id) {
return next(new Error(ErrorCodes.NOT_AUTHORISED));
}
return next();
public static notSelfRequest(req: Request, res: Response, next: NextFunction) {
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}
public static notSelfRequestOr2Admins(req:Request, res:Response, next:NextFunction) {
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}
if (req.session.user.id !== req.params.id) {
return next();
}
//TODO: fix it!
ObjectManagerRepository.getInstance().getUserManager().find({minRole: UserRoles.Admin}, (err, result) => {
if ((err) || (!result)) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
if (result.length <= 1) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
});
return next();
if (req.session.user.id === req.params.id) {
return next(new Error(ErrorCodes.NOT_AUTHORISED));
}
return next();
}
}
public static async notSelfRequestOr2Admins(req: Request, res: Response, next: NextFunction) {
if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) {
return next();
}
if (req.session.user.id !== req.params.id) {
return next();
}
//TODO: fix it!
try {
const result = await ObjectManagerRepository.getInstance().UserManager.find({minRole: UserRoles.Admin});
if (result.length <= 1) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
return next();
} catch (err) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
}
}

View File

@ -15,50 +15,52 @@ const LOG_TAG = "[DiskManager]";
pool.run(diskManagerTask);
export class DiskManager {
public static scanDirectory(relativeDirectoryName: string, cb: (error: any, result: DirectoryDTO) => void) {
Logger.silly(LOG_TAG, "scanning directory:", relativeDirectoryName);
let directoryName = path.basename(relativeDirectoryName);
let directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep);
let absoluteDirectoryName = path.join(ProjectPath.ImageFolder, relativeDirectoryName);
public static scanDirectory(relativeDirectoryName: string): Promise<DirectoryDTO> {
return new Promise((resolve, reject) => {
Logger.silly(LOG_TAG, "scanning directory:", relativeDirectoryName);
let directoryName = path.basename(relativeDirectoryName);
let directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep);
let absoluteDirectoryName = path.join(ProjectPath.ImageFolder, relativeDirectoryName);
let input = <DiskManagerTask.PoolInput>{
relativeDirectoryName,
directoryName,
directoryParent,
absoluteDirectoryName
};
let done = (error: any, result: DirectoryDTO) => {
if (error || !result) {
return cb(error, result);
}
let addDirs = (dir: DirectoryDTO) => {
dir.photos.forEach((ph) => {
ph.directory = dir;
});
dir.directories.forEach((d) => {
addDirs(d);
});
let input = <DiskManagerTask.PoolInput>{
relativeDirectoryName,
directoryName,
directoryParent,
absoluteDirectoryName
};
addDirs(result);
return cb(error, result);
};
let error = (error) => {
return cb(error, null);
};
let done = (error: any, result: DirectoryDTO) => {
if (error || !result) {
return reject(error);
}
let addDirs = (dir: DirectoryDTO) => {
dir.photos.forEach((ph) => {
ph.directory = dir;
});
dir.directories.forEach((d) => {
addDirs(d);
});
};
addDirs(result);
return resolve(result);
};
let error = (error) => {
return reject(error);
};
if (Config.Server.enableThreading == true) {
pool.send(input).on('done', done).on('error', error);
} else {
try {
diskManagerTask(input, done);
} catch (err) {
error(err);
if (Config.Server.enableThreading == true) {
pool.send(input).on('done', done).on('error', error);
} else {
try {
diskManagerTask(input, done);
} catch (err) {
error(err);
}
}
}
});
}
}

View File

@ -2,12 +2,15 @@ import {IUserManager} from "./interfaces/IUserManager";
import {IGalleryManager} from "./interfaces/IGalleryManager";
import {ISearchManager} from "./interfaces/ISearchManager";
import {MySQLConnection} from "./mysql/MySQLConnection";
import {ISharingManager} from "./interfaces/ISharingManager";
import {Logger} from "../Logger";
export class ObjectManagerRepository {
private _galleryManager: IGalleryManager;
private _userManager: IUserManager;
private _searchManager: ISearchManager;
private _sharingManager: ISharingManager;
private static _instance: ObjectManagerRepository = null;
@ -15,24 +18,24 @@ export class ObjectManagerRepository {
const GalleryManager = require("./memory/GalleryManager").GalleryManager;
const UserManager = require("./memory/UserManager").UserManager;
const SearchManager = require("./memory/SearchManager").SearchManager;
ObjectManagerRepository.getInstance().setGalleryManager(new GalleryManager());
ObjectManagerRepository.getInstance().setUserManager(new UserManager());
ObjectManagerRepository.getInstance().setSearchManager(new SearchManager());
const SharingManager = require("./memory/SharingManager").SharingManager;
ObjectManagerRepository.getInstance().GalleryManager = new GalleryManager();
ObjectManagerRepository.getInstance().UserManager = new UserManager();
ObjectManagerRepository.getInstance().SearchManager = new SearchManager();
ObjectManagerRepository.getInstance().SharingManager = new SharingManager();
}
public static InitMySQLManagers(): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
MySQLConnection.init().then(() => {
const GalleryManager = require("./mysql/GalleryManager").GalleryManager;
const UserManager = require("./mysql/UserManager").UserManager;
const SearchManager = require("./mysql/SearchManager").SearchManager;
ObjectManagerRepository.getInstance().setGalleryManager(new GalleryManager());
ObjectManagerRepository.getInstance().setUserManager(new UserManager());
ObjectManagerRepository.getInstance().setSearchManager(new SearchManager());
console.log("MySQL DB inited");
resolve(true);
}).catch(err => reject(err));
});
public static async InitMySQLManagers() {
await MySQLConnection.init();
const GalleryManager = require("./mysql/GalleryManager").GalleryManager;
const UserManager = require("./mysql/UserManager").UserManager;
const SearchManager = require("./mysql/SearchManager").SearchManager;
const SharingManager = require("./mysql/SharingManager").SharingManager;
ObjectManagerRepository.getInstance().GalleryManager = new GalleryManager();
ObjectManagerRepository.getInstance().UserManager = new UserManager();
ObjectManagerRepository.getInstance().SearchManager = new SearchManager();
ObjectManagerRepository.getInstance().SharingManager = new SharingManager();
Logger.debug("MySQL DB inited");
}
public static getInstance() {
@ -47,28 +50,36 @@ export class ObjectManagerRepository {
}
getGalleryManager(): IGalleryManager {
get GalleryManager(): IGalleryManager {
return this._galleryManager;
}
setGalleryManager(value: IGalleryManager) {
set GalleryManager(value: IGalleryManager) {
this._galleryManager = value;
}
getUserManager(): IUserManager {
get UserManager(): IUserManager {
return this._userManager;
}
setUserManager(value: IUserManager) {
set UserManager(value: IUserManager) {
this._userManager = value;
}
getSearchManager(): ISearchManager {
get SearchManager(): ISearchManager {
return this._searchManager;
}
setSearchManager(value: ISearchManager) {
set SearchManager(value: ISearchManager) {
this._searchManager = value;
}
get SharingManager(): ISharingManager {
return this._sharingManager;
}
set SharingManager(value: ISharingManager) {
this._sharingManager = value;
}
}

View File

@ -1,4 +1,4 @@
import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
export interface IGalleryManager {
listDirectory(relativeDirectoryName: string, cb: (error: any, result: DirectoryDTO) => void): void;
}
listDirectory(relativeDirectoryName: string): Promise<DirectoryDTO>;
}

View File

@ -0,0 +1,6 @@
import {SharingDTO} from "../../../common/entities/SharingDTO";
export interface ISharingManager {
findOne(filter: any): Promise<SharingDTO>;
createSharing(sharing: SharingDTO): Promise<SharingDTO>;
updateSharing(sharing: SharingDTO): Promise<SharingDTO>;
}

View File

@ -1,9 +1,9 @@
import {UserDTO, UserRoles} from "../../../common/entities/UserDTO";
export interface IUserManager {
findOne(filter: any, cb: (error: any, result: UserDTO) => void): void;
find(filter: any, cb: (error: any, result: Array<UserDTO>) => void): void;
createUser(user: UserDTO, cb: (error: any, result: UserDTO) => void): void;
deleteUser(id: number, cb: (error: any, result: string) => void): void;
changeRole(id: number, newRole: UserRoles, cb: (error: any) => void): void;
changePassword(request: any, cb: (error: any, result: string) => void): void;
}
findOne(filter: any): Promise<UserDTO>;
find(filter: any): Promise<UserDTO[]>;
createUser(user: UserDTO): Promise<UserDTO>;
deleteUser(id: number): Promise<UserDTO>;
changeRole(id: number, newRole: UserRoles): Promise<UserDTO>;
changePassword(request: any): Promise<void>;
}

View File

@ -4,10 +4,8 @@ import {DiskManager} from "../DiskManger";
export class GalleryManager implements IGalleryManager {
public listDirectory(relativeDirectoryName: string): Promise<DirectoryDTO> {
return DiskManager.scanDirectory(relativeDirectoryName);
}
public listDirectory(relativeDirectoryName: string, cb: (error: any, result: DirectoryDTO) => void) {
return DiskManager.scanDirectory(relativeDirectoryName, cb);
}
}
}

View File

@ -0,0 +1,19 @@
import {ISharingManager} from "../interfaces/ISharingManager";
import {SharingDTO} from "../../../common/entities/SharingDTO";
export class SharingManager implements ISharingManager {
findOne(filter: any): Promise<SharingDTO> {
throw new Error("not implemented");
}
createSharing(sharing: SharingDTO): Promise<SharingDTO> {
throw new Error("not implemented");
}
updateSharing(sharing: SharingDTO): Promise<SharingDTO> {
throw new Error("not implemented");
}
}

View File

@ -8,91 +8,87 @@ import * as path from "path";
export class UserManager implements IUserManager {
private db: any = null;
private db: any = null;
generateId(): string {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + s4() + s4();
generateId(): string {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
constructor() {
this.db = flatfile.sync(path.join(ProjectPath.Root, 'users.db'));
return s4() + s4() + s4() + s4();
}
if (!this.db.has("idCounter")) {
console.log("creating counter");
this.db.put("idCounter", 1);
}
if (!this.db.has("users")) {
this.db.put("users", []);
//TODO: remove defaults
this.createUser(<UserDTO>{name: "developer", password: "developer", role: UserRoles.Developer});
this.createUser(<UserDTO>{name: "admin", password: "admin", role: UserRoles.Admin});
this.createUser(<UserDTO>{name: "user", password: "user", role: UserRoles.User});
this.createUser(<UserDTO>{name: "guest", password: "guest", role: UserRoles.Guest});
}
constructor() {
this.db = flatfile.sync(path.join(ProjectPath.Root, 'users.db'));
if (!this.db.has("idCounter")) {
console.log("creating counter");
this.db.put("idCounter", 1);
}
if (!this.db.has("users")) {
this.db.put("users", []);
//TODO: remove defaults
this.createUser(<UserDTO>{name: "developer", password: "developer", role: UserRoles.Developer});
this.createUser(<UserDTO>{name: "admin", password: "admin", role: UserRoles.Admin});
this.createUser(<UserDTO>{name: "user", password: "user", role: UserRoles.User});
this.createUser(<UserDTO>{name: "guest", password: "guest", role: UserRoles.Guest});
}
public findOne(filter: any, cb: (error: any, result: UserDTO) => void) {
this.find(filter, (error, result: Array<UserDTO>) => {
if (error) {
return cb(error, null);
}
if (result.length == 0) {
return cb("UserDTO not found", null);
}
return cb(null, result[0]);
}
});
public async findOne(filter: any) {
const result = await this.find(filter);
if (result.length == 0) {
throw "UserDTO not found";
}
return result[0];
}
public find(filter: any, cb: (error: any, result: Array<UserDTO>) => void) {
public async find(filter: any) {
return this.db.get("users").filter((u: UserDTO) => Utils.equalsFilter(u, filter));
}
let users = this.db.get("users").filter((u: UserDTO) => Utils.equalsFilter(u, filter));
public async createUser(user: UserDTO) {
user.id = parseInt(this.db.get("idCounter")) + 1;
this.db.put("idCounter", user.id);
let users = this.db.get("users");
users.push(user);
this.db.put("users", users);
return cb(null, users);
return user;
}
public deleteUser(id: number) {
let deleted = this.db.get("users").filter((u: UserDTO) => u.id == id);
let users = this.db.get("users").filter((u: UserDTO) => u.id != id);
this.db.put("users", users);
if (deleted.length > 0) {
return deleted[0];
}
return null;
}
public createUser(user: UserDTO, cb: (error: any, result: UserDTO) => void = (e, r) => {
}) {
user.id = parseInt(this.db.get("idCounter")) + 1;
this.db.put("idCounter", user.id);
let users = this.db.get("users");
users.push(user);
public async changeRole(id: number, newRole: UserRoles): Promise<UserDTO> {
let users: Array<UserDTO> = this.db.get("users");
for (let i = 0; i < users.length; i++) {
if (users[i].id == id) {
users[i].role = newRole;
this.db.put("users", users);
return cb(null, user);
return users[i];
}
}
}
public deleteUser(id: number, cb: (error: any) => void) {
let users = this.db.get("users").filter((u: UserDTO) => u.id != id);
this.db.put("users", users);
return cb(null);
}
public async changePassword(request: any) {
throw new Error("not implemented"); //TODO: implement
}
public changeRole(id: number, newRole: UserRoles, cb: (error: any, result: string) => void) {
let users: Array<UserDTO> = this.db.get("users");
for (let i = 0; i < users.length; i++) {
if (users[i].id == id) {
users[i].role = newRole;
break;
}
}
this.db.put("users", users);
}
public changePassword(request: any, cb: (error: any, result: string) => void) {
throw new Error("not implemented"); //TODO: implement
}
}
}

View File

@ -11,158 +11,154 @@ import {ProjectPath} from "../../ProjectPath";
export class GalleryManager implements IGalleryManager {
public listDirectory(relativeDirectoryName, cb: (error: any, result: DirectoryDTO) => void) {
relativeDirectoryName = path.normalize(path.join("." + path.sep, relativeDirectoryName));
let directoryName = path.basename(relativeDirectoryName);
let directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep);
console.log("GalleryManager:listDirectory");
console.log(directoryName, directoryParent, path.dirname(relativeDirectoryName), ProjectPath.normalizeRelative(path.dirname(relativeDirectoryName)));
MySQLConnection.getConnection().then(async connection => {
public async listDirectory(relativeDirectoryName): Promise<DirectoryDTO> {
relativeDirectoryName = path.normalize(path.join("." + path.sep, relativeDirectoryName));
const directoryName = path.basename(relativeDirectoryName);
const directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep);
console.log("GalleryManager:listDirectory");
console.log(directoryName, directoryParent, path.dirname(relativeDirectoryName), ProjectPath.normalizeRelative(path.dirname(relativeDirectoryName)));
const connection = await MySQLConnection.getConnection();
let dir = await connection
.getRepository(DirectoryEntity)
.createQueryBuilder("directory")
.where("directory.name = :name AND directory.path = :path", {
name: directoryName,
path: directoryParent
})
.leftJoinAndSelect("directory.directories", "directories")
.leftJoinAndSelect("directory.photos", "photos")
.getOne();
let dir = await connection
.getRepository(DirectoryEntity)
.createQueryBuilder("directory")
.where("directory.name = :name AND directory.path = :path", {
name: directoryName,
path: directoryParent
})
.leftJoinAndSelect("directory.directories", "directories")
.leftJoinAndSelect("directory.photos", "photos")
.getOne();
if (dir && dir.scanned == true) {
if (dir.photos) {
for (let i = 0; i < dir.photos.length; i++) {
dir.photos[i].directory = dir;
dir.photos[i].metadata.keywords = <any>JSON.parse(<any>dir.photos[i].metadata.keywords);
dir.photos[i].metadata.cameraData = <any>JSON.parse(<any>dir.photos[i].metadata.cameraData);
dir.photos[i].metadata.positionData = <any>JSON.parse(<any>dir.photos[i].metadata.positionData);
dir.photos[i].metadata.size = <any>JSON.parse(<any>dir.photos[i].metadata.size);
dir.photos[i].readyThumbnails = [];
dir.photos[i].readyIcon = false;
}
}
if (dir && dir.scanned == true) {
if (dir.photos) {
for (let i = 0; i < dir.photos.length; i++) {
dir.photos[i].directory = dir;
dir.photos[i].metadata.keywords = <any>JSON.parse(<any>dir.photos[i].metadata.keywords);
dir.photos[i].metadata.cameraData = <any>JSON.parse(<any>dir.photos[i].metadata.cameraData);
dir.photos[i].metadata.positionData = <any>JSON.parse(<any>dir.photos[i].metadata.positionData);
dir.photos[i].metadata.size = <any>JSON.parse(<any>dir.photos[i].metadata.size);
dir.photos[i].readyThumbnails = [];
dir.photos[i].readyIcon = false;
}
}
//on the fly updating
this.indexDirectory(relativeDirectoryName).catch((err) => {
cb(null, dir); //WARNING: only on the fly indexing should happen after this point
console.error(err);
});
//on the fly updating
return this.indexDirectory(relativeDirectoryName, (err, res) => {
if (!!err || !res) {
console.error(err);
}
});
}
return this.indexDirectory(relativeDirectoryName, cb);
}).catch((error) => {
return cb(error, null);
});
return dir;
}
return this.indexDirectory(relativeDirectoryName);
public indexDirectory(relativeDirectoryName, cb: (error: any, result: DirectoryDTO) => void) {
DiskManager.scanDirectory(relativeDirectoryName, (err, scannedDirectory) => {
if (!!err || !scannedDirectory) {
return cb(err, null);
}
public indexDirectory(relativeDirectoryName): Promise<DirectoryDTO> {
return new Promise(async (resolve, reject) => {
try {
const scannedDirectory = await DiskManager.scanDirectory(relativeDirectoryName);
const connection = await MySQLConnection.getConnection();
//returning with the result
scannedDirectory.photos.forEach(p => p.readyThumbnails = []);
resolve(scannedDirectory);
//saving to db
let directoryRepository = connection.getRepository(DirectoryEntity);
let photosRepository = connection.getRepository(PhotoEntity);
let parentDir = await directoryRepository.createQueryBuilder("directory")
.where("directory.name = :name AND directory.path = :path", {
name: scannedDirectory.name,
path: scannedDirectory.path
}).getOne();
if (!!parentDir) {
parentDir.scanned = true;
parentDir.lastUpdate = Date.now();
parentDir = await directoryRepository.persist(parentDir);
} else {
(<DirectoryEntity>scannedDirectory).scanned = true;
parentDir = await directoryRepository.persist(<DirectoryEntity>scannedDirectory);
}
for (let i = 0; i < scannedDirectory.directories.length; i++) {
//TODO: simplify algorithm
let dir = await directoryRepository.createQueryBuilder("directory")
.where("directory.name = :name AND directory.path = :path", {
name: scannedDirectory.directories[i].name,
path: scannedDirectory.directories[i].path
}).getOne();
if (dir) {
dir.parent = parentDir;
await directoryRepository.persist(dir);
} else {
scannedDirectory.directories[i].parent = parentDir;
(<DirectoryEntity>scannedDirectory.directories[i]).scanned = false;
await directoryRepository.persist(<DirectoryEntity>scannedDirectory.directories[i]);
}
}
let indexedPhotos = await photosRepository.createQueryBuilder("photo")
.where("photo.directory = :dir", {
dir: parentDir.id
}).getMany();
let photosToSave = [];
for (let i = 0; i < scannedDirectory.photos.length; i++) {
let photo = null;
for (let j = 0; j < indexedPhotos.length; j++) {
if (indexedPhotos[j].name == scannedDirectory.photos[i].name) {
photo = indexedPhotos[j];
indexedPhotos.splice(j, 1);
break;
}
}
if (photo == null) {
scannedDirectory.photos[i].directory = null;
photo = Utils.clone(scannedDirectory.photos[i]);
scannedDirectory.photos[i].directory = scannedDirectory;
photo.directory = parentDir;
}
MySQLConnection.getConnection().then(async connection => {
//typeorm not supports recursive embended: TODO:fix it
let keyStr = <any>JSON.stringify(scannedDirectory.photos[i].metadata.keywords);
let camStr = <any>JSON.stringify(scannedDirectory.photos[i].metadata.cameraData);
let posStr = <any>JSON.stringify(scannedDirectory.photos[i].metadata.positionData);
let sizeStr = <any>JSON.stringify(scannedDirectory.photos[i].metadata.size);
//returning with the result
scannedDirectory.photos.forEach(p => p.readyThumbnails = []);
cb(null, scannedDirectory);
if (photo.metadata.keywords != keyStr ||
photo.metadata.cameraData != camStr ||
photo.metadata.positionData != posStr ||
photo.metadata.size != sizeStr) {
//saving to db
let directoryRepository = connection.getRepository(DirectoryEntity);
let photosRepository = connection.getRepository(PhotoEntity);
photo.metadata.keywords = keyStr;
photo.metadata.cameraData = camStr;
photo.metadata.positionData = posStr;
photo.metadata.size = sizeStr;
photosToSave.push(photo);
}
}
await photosRepository.persist(photosToSave);
await photosRepository.remove(indexedPhotos);
let parentDir = await directoryRepository.createQueryBuilder("directory")
.where("directory.name = :name AND directory.path = :path", {
name: scannedDirectory.name,
path: scannedDirectory.path
}).getOne();
} catch (error) {
return reject(error);
if (!!parentDir) {
parentDir.scanned = true;
parentDir.lastUpdate = Date.now();
parentDir = await directoryRepository.persist(parentDir);
} else {
(<DirectoryEntity>scannedDirectory).scanned = true;
parentDir = await directoryRepository.persist(<DirectoryEntity>scannedDirectory);
}
}
});
}
for (let i = 0; i < scannedDirectory.directories.length; i++) {
//TODO: simplify algorithm
let dir = await directoryRepository.createQueryBuilder("directory")
.where("directory.name = :name AND directory.path = :path", {
name: scannedDirectory.directories[i].name,
path: scannedDirectory.directories[i].path
}).getOne();
if (dir) {
dir.parent = parentDir;
await directoryRepository.persist(dir);
} else {
scannedDirectory.directories[i].parent = parentDir;
(<DirectoryEntity>scannedDirectory.directories[i]).scanned = false;
await directoryRepository.persist(<DirectoryEntity>scannedDirectory.directories[i]);
}
}
let indexedPhotos = await photosRepository.createQueryBuilder("photo")
.where("photo.directory = :dir", {
dir: parentDir.id
}).getMany();
let photosToSave = [];
for (let i = 0; i < scannedDirectory.photos.length; i++) {
let photo = null;
for (let j = 0; j < indexedPhotos.length; j++) {
if (indexedPhotos[j].name == scannedDirectory.photos[i].name) {
photo = indexedPhotos[j];
indexedPhotos.splice(j, 1);
break;
}
}
if (photo == null) {
scannedDirectory.photos[i].directory = null;
photo = Utils.clone(scannedDirectory.photos[i]);
scannedDirectory.photos[i].directory = scannedDirectory;
photo.directory = parentDir;
}
//typeorm not supports recursive embended: TODO:fix it
let keyStr = <any>JSON.stringify(scannedDirectory.photos[i].metadata.keywords);
let camStr = <any>JSON.stringify(scannedDirectory.photos[i].metadata.cameraData);
let posStr = <any>JSON.stringify(scannedDirectory.photos[i].metadata.positionData);
let sizeStr = <any>JSON.stringify(scannedDirectory.photos[i].metadata.size);
if (photo.metadata.keywords != keyStr ||
photo.metadata.cameraData != camStr ||
photo.metadata.positionData != posStr ||
photo.metadata.size != sizeStr) {
photo.metadata.keywords = keyStr;
photo.metadata.cameraData = camStr;
photo.metadata.positionData = posStr;
photo.metadata.size = sizeStr;
photosToSave.push(photo);
}
}
await photosRepository.persist(photosToSave);
await photosRepository.remove(indexedPhotos);
}).catch((error) => {
return cb(error, null);
});
});
}
}
}

View File

@ -5,72 +5,68 @@ import {UserRoles} from "../../../common/entities/UserDTO";
import {PhotoEntity, PhotoMetadataEntity} from "./enitites/PhotoEntity";
import {DirectoryEntity} from "./enitites/DirectoryEntity";
import {Config} from "../../../common/config/private/Config";
import {SharingEntity} from "./enitites/SharingEntity";
export class MySQLConnection {
constructor() {
constructor() {
}
private static connection: Connection = null;
public static async getConnection(): Promise<Connection> {
if (this.connection == null) {
this.connection = await createConnection({
driver: {
type: "mysql",
host: Config.Server.database.mysql.host,
port: 3306,
username: Config.Server.database.mysql.username,
password: Config.Server.database.mysql.password,
database: Config.Server.database.mysql.database
},
entities: [
UserEntity,
DirectoryEntity,
PhotoMetadataEntity,
PhotoEntity,
SharingEntity
],
autoSchemaSync: true,
logging: {
logQueries: true,
logOnlyFailedQueries: true,
logFailedQueryError: true,
logSchemaCreation: true
}
});
}
return this.connection;
private static connection: Connection = null;
}
public static getConnection(): Promise<Connection> {
return new Promise<Connection>((resolve, reject) => {
public static init(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.getConnection().then(async connection => {
if (this.connection != null) {
return resolve(this.connection);
}
let userRepository = connection.getRepository(UserEntity);
let admins = await userRepository.find({role: UserRoles.Admin});
if (admins.length == 0) {
let a = new UserEntity();
a.name = "admin";
a.password = "admin";
a.role = UserRoles.Admin;
await userRepository.persist(a);
}
createConnection({
driver: {
type: "mysql",
host: Config.Server.database.mysql.host,
port: 3306,
username: Config.Server.database.mysql.username,
password: Config.Server.database.mysql.password,
database: Config.Server.database.mysql.database
},
entities: [
UserEntity,
DirectoryEntity,
PhotoMetadataEntity,
PhotoEntity
],
autoSchemaSync: true,
logging: {
logQueries: true,
logOnlyFailedQueries: true,
logFailedQueryError: true,
logSchemaCreation: true
}
}).then((conn) => {
this.connection = conn;
return resolve(this.connection);
}).catch(err => reject(err));
});
}
public static init(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.getConnection().then(async connection => {
let userRepository = connection.getRepository(UserEntity);
let admins = await userRepository.find({role: UserRoles.Admin});
if (admins.length == 0) {
let a = new UserEntity();
a.name = "admin";
a.password = "admin";
a.role = UserRoles.Admin;
await userRepository.persist(a);
}
resolve();
}).catch(err => reject(err));
});
}
resolve();
}).catch(err => reject(err));
});
}
}
}

View File

@ -0,0 +1,54 @@
import {ISharingManager} from "../interfaces/ISharingManager";
import {SharingDTO} from "../../../common/entities/SharingDTO";
import {MySQLConnection} from "./MySQLConnection";
import {SharingEntity} from "./enitites/SharingEntity";
import {Config} from "../../../common/config/private/Config";
export class SharingManager implements ISharingManager {
private async removeExpiredLink() {
const connection = await MySQLConnection.getConnection();
return connection
.getRepository(SharingEntity)
.createQueryBuilder("share")
.where("expires < :now", {now: Date.now()})
.delete()
.execute();
}
async findOne(filter: any): Promise<SharingDTO> {
await this.removeExpiredLink();
const connection = await MySQLConnection.getConnection();
return await connection.getRepository(SharingEntity).findOne(filter);
}
async createSharing(sharing: SharingDTO): Promise<SharingDTO> {
await this.removeExpiredLink();
const connection = await MySQLConnection.getConnection();
return await connection.getRepository(SharingEntity).persist(sharing);
}
async updateSharing(inSharing: SharingDTO): Promise<SharingDTO> {
const connection = await MySQLConnection.getConnection();
let sharing = await connection.getRepository(SharingEntity).findOne({
id: inSharing.id,
creator: inSharing.creator.id,
path: inSharing.path
});
if (sharing.timeStamp < Date.now() - Config.Server.sharing.updateTimeout) {
throw "Sharing is locked, can't update anymore"
}
sharing.password = inSharing.password;
sharing.includeSubfolders = inSharing.includeSubfolders;
sharing.expires = inSharing.expires;
return await connection.getRepository(SharingEntity).persist(sharing);
}
}

View File

@ -5,79 +5,56 @@ import {MySQLConnection} from "./MySQLConnection";
export class UserManager implements IUserManager {
constructor() {
constructor() {
}
public async findOne(filter: any) {
const connection = await MySQLConnection.getConnection();
const user = (await connection.getRepository(UserEntity).findOne(filter));
if (user.permissions && user.permissions != null) {
user.permissions = <any>JSON.parse(<any>user.permissions);
}
return user;
};
public findOne(filter: any, cb: (error: any, result: UserDTO) => void) {
MySQLConnection.getConnection().then(async connection => {
let userRepository = connection.getRepository(UserEntity);
return cb(null, await userRepository.findOne(filter));
}).catch((error) => {
return cb(error, null);
});
public async find(filter: any) {
const connection = await MySQLConnection.getConnection();
return (await connection.getRepository(UserEntity).find(filter)).map(user => {
if (user.permissions && user.permissions != null) {
user.permissions = <any>JSON.parse(<any>user.permissions);
}
return user;
});
}
public async createUser(user: UserDTO) {
const connection = await MySQLConnection.getConnection();
if (user.permissions && user.permissions != null) {
user.permissions = <any>JSON.stringify(<any>user.permissions);
}
return await connection.getRepository(UserEntity).persist(user);
}
public find(filter: any, cb: (error: any, result: Array<UserDTO>) => void) {
MySQLConnection.getConnection().then(async connection => {
public async deleteUser(id: number) {
const connection = await MySQLConnection.getConnection();
const user = await connection.getRepository(UserEntity).findOne({id: id});
return await connection.getRepository(UserEntity).remove(user);
}
let userRepository = connection.getRepository(UserEntity);
return cb(null, await userRepository.find(filter));
public async changeRole(id: number, newRole: UserRoles) {
}).catch((error) => {
return cb(error, null);
});
}
const connection = await MySQLConnection.getConnection();
let userRepository = connection.getRepository(UserEntity);
const user = await userRepository.findOne({id: id});
user.role = newRole;
return await userRepository.persist(user);
public createUser(user: UserDTO, cb: (error: any, result: UserDTO) => void = (e, r) => {
}) {
MySQLConnection.getConnection().then(connection => {
}
let userRepository = connection.getRepository(UserEntity);
userRepository.persist(user).then(u => cb(null, u)).catch(err => cb(err, null));
public async changePassword(request: any) {
throw new Error("not implemented"); //TODO: implement
}
}).catch((error) => {
return cb(error, null);
});
}
public deleteUser(id: number, cb: (error: any) => void) {
MySQLConnection.getConnection().then(connection => {
let userRepository = connection.getRepository(UserEntity);
userRepository.findOne({id: id}).then((user) => {
userRepository.remove(user).catch(err => cb(err));
}).catch(err => cb(err));
}).catch((error) => {
return cb(error);
});
}
public changeRole(id: number, newRole: UserRoles, cb: (error: any, result: string) => void) {
MySQLConnection.getConnection().then(async connection => {
let userRepository = connection.getRepository(UserEntity);
let user = await userRepository.findOne({id: id});
user.role = newRole;
await userRepository.persist(user);
return cb(null, "ok");
}).catch((error) => {
return cb(error, null);
});
}
public changePassword(request: any, cb: (error: any, result: string) => void) {
throw new Error("not implemented"); //TODO: implement
}
}
}

View File

@ -0,0 +1,31 @@
import {Column, EmbeddableEntity, Embedded, Entity, ManyToOne, PrimaryGeneratedColumn} from "typeorm";
import {SharingDTO} from "../../../../common/entities/SharingDTO";
import {UserEntity} from "./UserEntity";
import {UserDTO} from "../../../../common/entities/UserDTO";
@Entity()
export class SharingEntity implements SharingDTO {
@PrimaryGeneratedColumn()
id: number;
@Column("string")
sharingKey: string;
@Column("string")
path: string;
@Column("string", {nullable: true})
password: string;
@Column("number")
expires: number;
@Column("number")
timeStamp: number;
@Column("boolean")
includeSubfolders: boolean;
@ManyToOne(type => UserEntity)
creator: UserDTO;
}

View File

@ -1,23 +1,26 @@
import {UserDTO, UserRoles} from "../../../../common/entities/UserDTO";
import {Entity, Column, PrimaryGeneratedColumn} from "typeorm";
import {Column, Entity, PrimaryGeneratedColumn} from "typeorm";
@Entity()
export class UserEntity implements UserDTO {
@PrimaryGeneratedColumn()
id: number;
@PrimaryGeneratedColumn()
id: number;
@Column({
length: 500
})
name: string;
@Column({
length: 500
})
name: string;
@Column({
length: 500
})
password: string;
@Column({
length: 500
})
password: string;
@Column("int")
role: UserRoles;
@Column("int")
role: UserRoles;
}
@Column("string")
permissions: string[];
}

View File

@ -19,6 +19,7 @@ export class GalleryRouter {
private static addDirectoryList(app) {
app.get(["/api/gallery/content/:directory(*)", "/api/gallery/", "/api/gallery//"],
AuthenticationMWs.authenticate,
AuthenticationMWs.authoriseDirectory,
GalleryMWs.listDirectory,
ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences,

View File

@ -3,6 +3,7 @@ import {NextFunction, Request, Response} from "express";
import * as _path from "path";
import {Utils} from "../../common/Utils";
import {Config} from "../../common/config/private/Config";
import {ProjectPath} from "../ProjectPath";
export class PublicRouter {
@ -24,11 +25,11 @@ export class PublicRouter {
app.get('/config_inject.js', (req: Request, res: Response) => {
res.render(_path.resolve(__dirname, './../../dist/config_inject.ejs'), res.tpl);
});
app.get(['/', '/login', "/gallery*", "/admin", "/search*"], (req: Request, res: Response) => {
app.get(['/', '/login', "/gallery*", "/share*", "/admin", "/search*"], (req: Request, res: Response) => {
res.sendFile(_path.resolve(__dirname, './../../dist/index.html'));
});
app.use(_express.static(_path.resolve(__dirname, './../../dist')));
app.use(_express.static(ProjectPath.FrontendFolder));
app.use('/node_modules', _express.static(_path.resolve(__dirname, './../../node_modules')));
app.use('/common', _express.static(_path.resolve(__dirname, './../../common')));

View File

@ -1,26 +1,40 @@
import {AuthenticationMWs} from "../middlewares/user/AuthenticationMWs";
import {UserRoles} from "../../common/entities/UserDTO";
import {RenderingMWs} from "../middlewares/RenderingMWs";
import {SharingMWs} from "../middlewares/SharingMWs";
export class SharingRouter {
public static route(app: any) {
this.addGetSharing(app);
this.addCreateSharing(app);
this.addUpdateSharing(app);
}
private static addGetSharing(app) {
app.get("/api/share/:directory",
app.get("/api/share/:sharingKey",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.User)
//TODO: implement
AuthenticationMWs.authorise(UserRoles.Guest),
SharingMWs.getSharing,
RenderingMWs.renderSharing
);
};
private static addCreateSharing(app) {
app.post(["/api/share/:directory(*)", "/api/share/", "/api/share//"],
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.User),
SharingMWs.createSharing,
RenderingMWs.renderSharing
);
};
private static addUpdateSharing(app) {
app.post("/api/share/:directory",
app.put(["/api/share/:directory(*)", "/api/share/", "/api/share//"],
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.User)
//TODO: implement
AuthenticationMWs.authorise(UserRoles.User),
SharingMWs.updateSharing,
RenderingMWs.renderSharing
);
};

View File

@ -13,6 +13,7 @@ import {Logger} from "./Logger";
import {Config} from "../common/config/private/Config";
import {DatabaseType, ThumbnailProcessingLib} from "../common/config/private/IPrivateConfig";
import {LoggerRouter} from "./routes/LoggerRouter";
import {ProjectPath} from "./ProjectPath";
const LOG_TAG = "[server]";
export class Server {
@ -88,7 +89,11 @@ export class Server {
if (Config.Server.thumbnail.processingLibrary == ThumbnailProcessingLib.gm) {
try {
const gm = require("gm");
gm(1, 1).stream((err) => {
gm(ProjectPath.FrontendFolder + "/assets/icon.png").size((err, value) => {
console.log(err, value);
if (!err) {
return;
}
Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] gm module error: ", err);
Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." +
" 'gm' node module is not found." +

View File

@ -26,11 +26,14 @@ export interface ThumbnailConfig {
processingLibrary: ThumbnailProcessingLib;
qualityPriority: boolean;
}
export interface SharingConfig {
updateTimeout: number;
}
export interface ServerConfig {
port: number;
imagesFolder: string;
thumbnail: ThumbnailConfig;
database: DataBaseConfig;
enableThreading: boolean;
sharing: SharingConfig;
}

View File

@ -25,6 +25,9 @@ export class PrivateConfigClass extends PublicConfigClass {
}
},
sharing: {
updateTimeout: 1000 * 60 * 5
},
enableThreading: true
};

View File

@ -4,10 +4,16 @@ interface SearchConfig {
autocompleteEnabled: boolean
}
interface SharingConfig {
enabled: boolean;
passwordProtected: boolean;
}
interface ClientConfig {
iconSize: number;
thumbnailSizes: Array<number>;
Search: SearchConfig;
Sharing: SharingConfig;
concurrentThumbnailGenerations: number;
enableCache: boolean;
enableOnScrollRendering: boolean;
@ -29,6 +35,10 @@ export class PublicConfigClass {
instantSearchEnabled: true,
autocompleteEnabled: true
},
Sharing: {
enabled: true,
passwordProtected: true
},
concurrentThumbnailGenerations: 1,
enableCache: false,
enableOnScrollRendering: true,

View File

@ -2,17 +2,20 @@ export enum ErrorCodes{
NOT_AUTHENTICATED = 0,
ALREADY_AUTHENTICATED = 1,
NOT_AUTHORISED = 2,
CREDENTIAL_NOT_FOUND = 3,
PERMISSION_DENIED = 3,
CREDENTIAL_NOT_FOUND = 4,
USER_CREATION_ERROR = 4,
USER_CREATION_ERROR = 5,
GENERAL_ERROR = 5,
THUMBNAIL_GENERATION_ERROR = 6,
SERVER_ERROR = 7,
GENERAL_ERROR = 6,
THUMBNAIL_GENERATION_ERROR = 7,
SERVER_ERROR = 8,
USER_MANAGEMENT_DISABLED = 8
USER_MANAGEMENT_DISABLED = 9,
INPUT_ERROR = 10
}

View File

@ -0,0 +1,19 @@
import {UserDTO} from "./UserDTO";
export interface SharingDTO {
id: number;
path: string;
sharingKey: string;
password: string;
expires: number;
timeStamp: number;
includeSubfolders: boolean;
creator: UserDTO;
}
export interface CreateSharingDTO {
id?: number;
password: string;
valid: number;
includeSubfolders: boolean;
}

View File

@ -1,5 +1,8 @@
import {DirectoryDTO} from "./DirectoryDTO";
import {Utils} from "../Utils";
export enum UserRoles{
Guest = 1,
Guest = 0,
TrustedGuest = 1,
User = 2,
Admin = 3,
Developer = 4,
@ -11,4 +14,33 @@ export interface UserDTO {
name: string;
password: string;
role: UserRoles;
permissions: string[]; //user can only see these permissions. if ends with *, its recursive
}
export module UserUtil {
export const isPathAvailable = (path: string, permissions: string[]): boolean => {
if (permissions == null || permissions.length == 0 || permissions[0] == "/") {
return true;
}
for (let i = 0; i < permissions.length; i++) {
let permission = permissions[i];
if (permission[permission.length - 1] == "*") {
permission = permission.slice(0, -1);
if (path.startsWith(permission)) {
return true
}
} else {
if (path == permission) {
return true
}
}
}
return false;
};
export const isDirectoryAvailable = (direcotry: DirectoryDTO, permissions: string[]): boolean => {
return isPathAvailable(Utils.concatUrls(direcotry.path, direcotry.name), permissions);
};
}

View File

@ -16,7 +16,7 @@ export class AdminComponent implements OnInit {
}
ngOnInit() {
if (!this._authService.isAuthenticated() || this._authService.getUser().role < UserRoles.Admin) {
if (!this._authService.isAuthenticated() || this._authService.user.value.role < UserRoles.Admin) {
this._router.navigate(['login']);
return;
}

View File

@ -15,7 +15,7 @@ export class AppComponent implements OnInit {
}
ngOnInit() {
this._authenticationService.OnUserChanged.on((user: UserDTO) => {
this._authenticationService.user.subscribe((user: UserDTO) => {
if (user != null) {
if (this._router.isActive('login', true)) {
console.log("routing");

View File

@ -33,6 +33,9 @@ import {OverlayService} from "./gallery/overlay.service";
import {Config} from "../../common/config/public/Config";
import {LAZY_MAPS_API_CONFIG} from "@agm/core/services";
import {SlimLoadingBarModule} from "ng2-slim-loading-bar";
import {GalleryShareComponent} from "./gallery/share/share.gallery.component";
import {ShareLoginComponent} from "./sharelogin/share-login.component";
import {ShareService} from "./gallery/share.service";
@Injectable()
@ -55,6 +58,7 @@ export class GoogleMapsConfig {
],
declarations: [AppComponent,
LoginComponent,
ShareLoginComponent,
AdminComponent,
GalleryComponent,
FrameComponent,
@ -68,6 +72,7 @@ export class GoogleMapsConfig {
GalleryMapLightboxComponent,
FrameComponent,
GallerySearchComponent,
GalleryShareComponent,
GalleryNavigatorComponent,
GalleryPhotoComponent,
FrameComponent,
@ -75,6 +80,7 @@ export class GoogleMapsConfig {
providers: [
{provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig},
NetworkService,
ShareService,
UserService,
GalleryCacheService,
GalleryService,

View File

@ -3,12 +3,17 @@ import {RouterModule, Routes} from "@angular/router";
import {LoginComponent} from "./login/login.component";
import {GalleryComponent} from "./gallery/gallery.component";
import {AdminComponent} from "./admin/admin.component";
import {ShareLoginComponent} from "./sharelogin/share-login.component";
const ROUTES: Routes = [
{
path: 'login',
component: LoginComponent
},
{
path: 'shareLogin',
component: ShareLoginComponent
},
{
path: 'admin',
component: AdminComponent
@ -25,6 +30,10 @@ const ROUTES: Routes = [
path: 'search/:searchText',
component: GalleryComponent
},
{
path: 'share/:sharingKey',
component: GalleryComponent
},
{path: '', redirectTo: '/login', pathMatch: 'full'}
];

View File

@ -16,9 +16,10 @@
<li class="active"><a [routerLink]="['/gallery','/']">Gallery</a></li>
<li><a [routerLink]="['/admin']">Admin</a></li>
</ul>
<ul class="nav navbar-nav navbar-right" *ngIf="authenticationRequired">
<li>
<p class="navbar-text" *ngIf="user">{{user.name}}</p>
<p class="navbar-text" *ngIf="user.value">{{user.value.name}}</p>
</li>
<li>
<a style="cursor: pointer" (click)="logout()">Logout</a>

View File

@ -3,6 +3,7 @@ import {RouterLink} from "@angular/router";
import {AuthenticationService} from "../model/network/authentication.service";
import {UserDTO} from "../../../common/entities/UserDTO";
import {Config} from "../../../common/config/public/Config";
import {BehaviorSubject} from "rxjs/BehaviorSubject";
@Component({
selector: 'app-frame',
@ -13,11 +14,11 @@ import {Config} from "../../../common/config/public/Config";
})
export class FrameComponent {
user: UserDTO;
user: BehaviorSubject<UserDTO>;
authenticationRequired: boolean = false;
constructor(private _authService: AuthenticationService) {
this.user = this._authService.getUser();
this.user = this._authService.user;
this.authenticationRequired = Config.Client.authenticationRequired;
}

View File

@ -1,21 +1,21 @@
<a #dirContainer class="button btn btn-default" [routerLink]="['/gallery', getDirectoryPath()]"
[queryParams]="_shareService.isSharing() ? { sk: _shareService.getSharingKey() }:{}"
style="display: inline-block;">
<div class="photo-container">
<div class="photo" *ngIf="thumbnail && thumbnail.Available"
[style.background-image]="getSanitizedThUrl()"></div>
<div class="photo-container">
<div class="photo" *ngIf="thumbnail && thumbnail.Available"
[style.background-image]="getSanitizedThUrl()"></div>
<span *ngIf="!thumbnail || !thumbnail.Available" class="glyphicon glyphicon-folder-open no-image"
aria-hidden="true">
<span *ngIf="!thumbnail || !thumbnail.Available" class="glyphicon glyphicon-folder-open no-image"
aria-hidden="true">
</span>
</div>
<!--Info box -->
<div #info class="info">
<div class="photo-name">{{directory.name}}</div>
</div>
<!--Info box -->
<div #info class="info">
<div class="photo-name">{{directory.name}}</div>
</div>
</div>
</a>

View File

@ -5,6 +5,7 @@ import {RouterLink} from "@angular/router";
import {Utils} from "../../../../common/Utils";
import {Photo} from "../Photo";
import {Thumbnail, ThumbnailManagerService} from "../thumnailManager.service";
import {ShareService} from "../share.service";
@Component({
selector: 'gallery-directory',
@ -17,7 +18,10 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy {
@ViewChild("dirContainer") container: ElementRef;
thumbnail: Thumbnail = null;
constructor(private thumbnailService: ThumbnailManagerService, private _sanitizer: DomSanitizer) {
constructor(private thumbnailService: ThumbnailManagerService,
private _sanitizer: DomSanitizer,
public _shareService: ShareService) {
}
ngOnInit() {

View File

@ -2,6 +2,7 @@
<app-frame>
<div navbar>
<gallery-share *ngIf="showShare"></gallery-share>
<gallery-search #search *ngIf="showSearchBar"></gallery-search>
</div>

View File

@ -1,4 +1,4 @@
import {Component, OnInit, ViewChild} from "@angular/core";
import {Component, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {AuthenticationService} from "../model/network/authentication.service";
import {ActivatedRoute, Params, Router} from "@angular/router";
import {GalleryService} from "./gallery.service";
@ -8,87 +8,119 @@ import {SearchTypes} from "../../../common/entities/AutoCompleteItem";
import {Config} from "../../../common/config/public/Config";
import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
import {SearchResultDTO} from "../../../common/entities/SearchResult";
import {ShareService} from "./share.service";
@Component({
selector: 'gallery',
templateUrl: './gallery.component.html',
styleUrls: ['./gallery.component.css']
})
export class GalleryComponent implements OnInit {
export class GalleryComponent implements OnInit, OnDestroy {
@ViewChild(GallerySearchComponent) search: GallerySearchComponent;
@ViewChild(GalleryGridComponent) grid: GalleryGridComponent;
public showSearchBar: boolean = true;
public showShare: boolean = true;
public directories: DirectoryDTO[] = [];
public isPhotoWithLocation = false;
private subscription = {
content: null,
route: null
};
constructor(public _galleryService: GalleryService,
private _authService: AuthenticationService,
private _router: Router,
private shareService: ShareService,
private _route: ActivatedRoute) {
this.showSearchBar = Config.Client.Search.searchEnabled;
this.showShare = Config.Client.Sharing.enabled;
}
ngOnInit() {
if (!this._authService.isAuthenticated()) {
this._router.navigate(['login']);
if (!this._authService.isAuthenticated() &&
(!this.shareService.isSharing() ||
(this.shareService.isSharing() && Config.Client.Sharing.passwordProtected == true))
) {
if (this.shareService.isSharing()) {
this._router.navigate(['shareLogin']);
} else {
this._router.navigate(['login']);
}
return;
}
this._galleryService.content.subscribe((content) => {
const dirSorter = (a: DirectoryDTO, b: DirectoryDTO) => {
return a.name.localeCompare(b.name);
};
const tmp = <DirectoryDTO | SearchResultDTO>(content.searchResult || content.directory || {
directories: [],
photos: []
});
this.directories = tmp.directories.sort(dirSorter);
this.isPhotoWithLocation = false;
for (let i = 0; i < tmp.photos.length; i++) {
if (tmp.photos[i].metadata &&
tmp.photos[i].metadata.positionData &&
tmp.photos[i].metadata.positionData.GPSData &&
tmp.photos[i].metadata.positionData.GPSData.longitude
) {
this.isPhotoWithLocation = true;
break;
}
}
});
this._route.params
.subscribe((params: Params) => {
let searchText = params['searchText'];
if (searchText && searchText != "") {
console.log("searching");
let typeString = params['type'];
if (typeString && typeString != "") {
console.log("with type");
let type: SearchTypes = <any>SearchTypes[typeString];
this._galleryService.search(searchText, type);
return;
}
this._galleryService.search(searchText);
return;
}
let directoryName = params['directory'];
directoryName = directoryName ? directoryName : "";
this._galleryService.getDirectory(directoryName);
});
this.subscription.content = this._galleryService.content.subscribe(this.onContentChange);
this.subscription.route = this._route.params.subscribe(this.onRoute);
}
ngOnDestroy() {
if (this.subscription.content !== null) {
this.subscription.content.unsubscribe();
}
if (this.subscription.route !== null) {
this.subscription.route.unsubscribe();
}
}
private onContentChange = (content) => {
const dirSorter = (a: DirectoryDTO, b: DirectoryDTO) => {
return a.name.localeCompare(b.name);
};
const tmp = <DirectoryDTO | SearchResultDTO>(content.searchResult || content.directory || {
directories: [],
photos: []
});
this.directories = tmp.directories.sort(dirSorter);
this.isPhotoWithLocation = false;
for (let i = 0; i < tmp.photos.length; i++) {
if (tmp.photos[i].metadata &&
tmp.photos[i].metadata.positionData &&
tmp.photos[i].metadata.positionData.GPSData &&
tmp.photos[i].metadata.positionData.GPSData.longitude
) {
this.isPhotoWithLocation = true;
break;
}
}
};
private onRoute = async (params: Params) => {
console.log("onRoute", params);
const searchText = params['searchText'];
if (searchText && searchText != "") {
console.log("searching");
let typeString = params['type'];
if (typeString && typeString != "") {
console.log("with type");
let type: SearchTypes = <any>SearchTypes[typeString];
this._galleryService.search(searchText, type);
return;
}
this._galleryService.search(searchText);
return;
}
if (params['sharingKey'] && params['sharingKey'] != "") {
const sharing = await this._galleryService.getSharing(this.shareService.getSharingKey());
this._router.navigate(['/gallery', sharing.path], {queryParams: {sk: this.shareService.getSharingKey()}});
return;
}
let directoryName = params['directory'];
directoryName = directoryName || "";
this._galleryService.getDirectory(directoryName);
};
onLightboxLastElement() {
this.grid.renderARow();

View File

@ -1,12 +1,14 @@
import {Injectable} from "@angular/core";
import {NetworkService} from "../model/network/network.service";
import {Message} from "../../../common/entities/Message";
import {ContentWrapper} from "../../../common/entities/ConentWrapper";
import {PhotoDTO} from "../../../common/entities/PhotoDTO";
import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
import {SearchTypes} from "../../../common/entities/AutoCompleteItem";
import {GalleryCacheService} from "./cache.gallery.service";
import {BehaviorSubject} from "rxjs/BehaviorSubject";
import {SharingDTO} from "../../../common/entities/SharingDTO";
import {Config} from "../../../common/config/public/Config";
import {ShareService} from "./share.service";
@Injectable()
export class GalleryService {
@ -15,7 +17,9 @@ export class GalleryService {
private lastDirectory: DirectoryDTO;
private searchId: any;
constructor(private networkService: NetworkService, private galleryCacheService: GalleryCacheService) {
constructor(private networkService: NetworkService,
private galleryCacheService: GalleryCacheService,
private _shareService: ShareService) {
this.content = new BehaviorSubject<ContentWrapper>(new ContentWrapper());
}
@ -23,7 +27,7 @@ export class GalleryService {
directory: null
};
public async getDirectory(directoryName: string): Promise<Message<ContentWrapper>> {
public async getDirectory(directoryName: string): Promise<ContentWrapper> {
const content = new ContentWrapper();
content.directory = this.galleryCacheService.getDirectory(directoryName);
@ -32,70 +36,71 @@ export class GalleryService {
this.content.next(content);
this.lastRequest.directory = directoryName;
let message: Message<ContentWrapper> = await this.networkService.getJson<Message<ContentWrapper>>("/gallery/content/" + directoryName);
if (!message.error && message.result) {
this.galleryCacheService.setDirectory(message.result.directory); //save it before adding references
if (this.lastRequest.directory != directoryName) {
return;
let cw: ContentWrapper = null;
if (Config.Client.Sharing.enabled == true) {
if (this._shareService.isSharing()) {
cw = await this.networkService.getJson<ContentWrapper>("/gallery/content/" + directoryName + "?sk=" + this._shareService.getSharingKey());
}
//Add references
let addDir = (dir: DirectoryDTO) => {
dir.photos.forEach((photo: PhotoDTO) => {
photo.directory = dir;
});
dir.directories.forEach((directory: DirectoryDTO) => {
addDir(directory);
directory.parent = dir;
});
};
addDir(message.result.directory);
this.lastDirectory = message.result.directory;
this.content.next(message.result);
}
if (cw == null) {
cw = await this.networkService.getJson<ContentWrapper>("/gallery/content/" + directoryName);
}
return message;
this.galleryCacheService.setDirectory(cw.directory); //save it before adding references
if (this.lastRequest.directory != directoryName) {
return;
}
//Add references
let addDir = (dir: DirectoryDTO) => {
dir.photos.forEach((photo: PhotoDTO) => {
photo.directory = dir;
});
dir.directories.forEach((directory: DirectoryDTO) => {
addDir(directory);
directory.parent = dir;
});
};
addDir(cw.directory);
this.lastDirectory = cw.directory;
this.content.next(cw);
return cw;
}
//TODO: cache
public search(text: string, type?: SearchTypes): Promise<Message<ContentWrapper>> {
public async search(text: string, type?: SearchTypes): Promise<ContentWrapper> {
clearTimeout(this.searchId);
if (text === null || text === '') {
return Promise.resolve(new Message(null, null));
return null
}
let queryString = "/search/" + text;
if (type) {
queryString += "?type=" + type;
}
return this.networkService.getJson(queryString).then(
(message: Message<ContentWrapper>) => {
if (!message.error && message.result) {
this.content.next(message.result);
}
return message;
});
const cw: ContentWrapper = await this.networkService.getJson<ContentWrapper>(queryString);
this.content.next(cw);
return cw;
}
//TODO: cache (together with normal search)
public instantSearch(text: string): Promise<Message<ContentWrapper>> {
public async instantSearch(text: string): Promise<ContentWrapper> {
if (text === null || text === '') {
const content = new ContentWrapper();
content.directory = this.lastDirectory;
content.searchResult = null;
this.content.next(content);
clearTimeout(this.searchId);
return Promise.resolve(new Message(null, null));
return null
}
if (this.searchId != null) {
@ -107,14 +112,14 @@ export class GalleryService {
this.searchId = null;
}, 3000); //TODO: set timeout to config
return this.networkService.getJson("/instant-search/" + text).then(
(message: Message<ContentWrapper>) => {
if (!message.error && message.result) {
this.content.next(message.result);
}
return message;
});
const cw = await this.networkService.getJson<ContentWrapper>("/instant-search/" + text);
this.content.next(cw);
return cw;
}
public async getSharing(sharingKey: string): Promise<SharingDTO> {
return this.networkService.getJson<SharingDTO>("/share/" + sharingKey);
}
}

View File

@ -1,7 +1,8 @@
<ol id="directory-path" class="breadcrumb">
<li *ngFor="let path of routes">
<a *ngIf="path.route" [routerLink]="['/gallery',path.route]">{{path.name}}</a>
<span *ngIf="!path.route">{{path.name}}</span>
</li>
<li *ngFor="let path of routes">
<a *ngIf="path.route" [routerLink]="['/gallery',path.route]"
[queryParams]="_shareService.isSharing() ? { sk: _shareService.getSharingKey() }:{}">{{path.name}}</a>
<span *ngIf="!path.route">{{path.name}}</span>
</li>
</ol>

View File

@ -1,6 +1,9 @@
import {Component, Input, OnChanges} from "@angular/core";
import {DirectoryDTO} from "../../../../common/entities/DirectoryDTO";
import {RouterLink} from "@angular/router";
import {UserUtil} from "../../../../common/entities/UserDTO";
import {AuthenticationService} from "../../model/network/authentication.service";
import {ShareService} from "../share.service";
@Component({
selector: 'gallery-navbar',
@ -10,9 +13,10 @@ import {RouterLink} from "@angular/router";
export class GalleryNavigatorComponent implements OnChanges {
@Input() directory: DirectoryDTO;
routes: Array<any> = [];
routes: Array<NavigatorPath> = [];
constructor() {
constructor(private _authService: AuthenticationService,
public _shareService: ShareService) {
}
@ -38,24 +42,24 @@ export class GalleryNavigatorComponent implements OnChanges {
}
}
let arr: any = [];
const user = this._authService.user.value;
let arr: NavigatorPath[] = [];
//create root link
if (dirs.length == 0) {
arr.push({name: "Images", route: null});
} else {
arr.push({name: "Images", route: "/"});
arr.push({name: "Images", route: UserUtil.isPathAvailable("/", user.permissions) ? "/" : null});
}
//create rest navigation
dirs.forEach((name, index) => {
let route = dirs.slice(0, dirs.indexOf(name) + 1).join("/");
const route = dirs.slice(0, dirs.indexOf(name) + 1).join("/");
if (dirs.length - 1 == index) {
arr.push({name: name, route: null});
} else {
arr.push({name: name, route: route});
arr.push({name: name, route: UserUtil.isPathAvailable(route, user.permissions) ? route : null});
}
});
@ -68,3 +72,8 @@ export class GalleryNavigatorComponent implements OnChanges {
}
interface NavigatorPath {
name: string;
route: string;
}

View File

@ -1,7 +1,6 @@
import {Injectable} from "@angular/core";
import {NetworkService} from "../../model/network/network.service";
import {AutoCompleteItem} from "../../../../common/entities/AutoCompleteItem";
import {Message} from "../../../../common/entities/Message";
@Injectable()
export class AutoCompleteService {
@ -10,8 +9,8 @@ export class AutoCompleteService {
constructor(private _networkService: NetworkService) {
}
public autoComplete(text: string): Promise<Message<Array<AutoCompleteItem>>> {
return this._networkService.getJson("/autocomplete/" + text);
public autoComplete(text: string): Promise<Array<AutoCompleteItem>> {
return this._networkService.getJson<Array<AutoCompleteItem>>("/autocomplete/" + text);
}

View File

@ -1,4 +1,4 @@
<div class="col-sm-4 col-md-5 pull-right">
<div class="col-sm-3 col-md-4 pull-right">
<form class="navbar-form" role="search" #SearchForm="ngForm">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search" (keyup)="onSearchChange($event)"

View File

@ -2,7 +2,6 @@ import {Component} from "@angular/core";
import {AutoCompleteService} from "./autocomplete.service";
import {AutoCompleteItem, SearchTypes} from "../../../../common/entities/AutoCompleteItem";
import {ActivatedRoute, Params, RouterLink} from "@angular/router";
import {Message} from "../../../../common/entities/Message";
import {GalleryService} from "../gallery.service";
import {Config} from "../../../../common/config/public/Config";
@ -23,20 +22,27 @@ export class GallerySearchComponent {
SearchTypes: any = [];
private subscription = null;
constructor(private _autoCompleteService: AutoCompleteService,
private _galleryService: GalleryService,
private _route: ActivatedRoute) {
this.SearchTypes = SearchTypes;
this._route.params
.subscribe((params: Params) => {
let searchText = params['searchText'];
if (searchText && searchText != "") {
this.searchText = searchText;
}
this.subscription = this._route.params.subscribe((params: Params) => {
let searchText = params['searchText'];
if (searchText && searchText != "") {
this.searchText = searchText;
}
});
}
});
ngOnDestroy() {
if (this.subscription !== null) {
this.subscription.unsubscribe()
}
}
onSearchChange(event: KeyboardEvent) {
@ -87,19 +93,19 @@ export class GallerySearchComponent {
this.autoCompleteItems = [];
}
private autocomplete(searchText: string) {
private async autocomplete(searchText: string) {
if (!Config.Client.Search.autocompleteEnabled) {
return
}
if (searchText.trim().length > 0) {
this._autoCompleteService.autoComplete(searchText).then((message: Message<Array<AutoCompleteItem>>) => {
if (message.error) {
//TODO: implement
console.error(message.error);
return;
}
this.showSuggestions(message.result, searchText);
});
try {
const items = await this._autoCompleteService.autoComplete(searchText);
this.showSuggestions(items, searchText);
} catch (error) {
console.error(error);
}
} else {
this.emptyAutoComplete();
}

View File

@ -0,0 +1,74 @@
import {Injectable} from "@angular/core";
import {NetworkService} from "../model/network/network.service";
import {CreateSharingDTO, SharingDTO} from "../../../common/entities/SharingDTO";
import {Router, RoutesRecognized} from "@angular/router";
@Injectable()
export class ShareService {
param = null;
queryParam = null;
sharingKey = null;
inited = false;
public ReadyPR: Promise<void>;
private resolve;
constructor(private _networkService: NetworkService, private router: Router) {
this.ReadyPR = new Promise((resolve) => {
if (this.inited == true) {
return resolve();
}
this.resolve = resolve;
});
this.router.events.subscribe(val => {
if (val instanceof RoutesRecognized) {
this.param = val.state.root.firstChild.params["sharingKey"] || null;
this.queryParam = val.state.root.firstChild.queryParams["sk"] || null;
this.sharingKey = this.param || this.queryParam;
if (this.resolve) {
this.resolve();
console.log("resolving", this.sharingKey);
this.inited = true;
}
}
});
}
public wait(): Promise<void> {
return this.ReadyPR;
}
public getSharing(dir: string, includeSubfolders: boolean, valid: number): Promise<SharingDTO> {
return this._networkService.postJson("/share/" + dir, {
createSharing: <CreateSharingDTO>{
includeSubfolders: includeSubfolders,
valid: valid
}
});
}
public updateSharing(dir: string, sharingId: number, includeSubfolders: boolean, valid: number): Promise<SharingDTO> {
return this._networkService.putJson("/share/" + dir, {
updateSharing: <CreateSharingDTO>{
id: sharingId,
includeSubfolders: includeSubfolders,
valid: valid
}
});
}
public getSharingKey() {
return this.sharingKey;
}
public isSharing(): boolean {
return this.sharingKey != null;
}
}

View File

@ -0,0 +1,4 @@
.modal {
z-index: 9999;
}

View File

@ -0,0 +1,73 @@
<div class="col-sm-2 col-md-2 pull-right">
<button id="shareButton" class="btn btn-default navbar-btn btn-link" type="button"
data-toggle="modal" data-target="#shareModal" [disabled]="!enabled" (click)="get()">
<span class="glyphicon glyphicon-share-alt"></span>
Share
</button>
</div>
<!-- sharing Modal-->
<div class="modal fade" id="shareModal" tabindex="-1" role="dialog" aria-labelledby="shareModalLabel"
data-backdrop="false"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="shareModalLabel">Share</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm-10">
<input id="shareLink" name="shareLink" placeholder="link" class="form-control input-md" type="text"
[ngModel]="url">
</div>
<div class="col-sm-2 pull-right">
<button id="copyButton" name="copyButton" data-clipboard-target="shareLink" class="btn btn-primary">Copy
</button>
</div>
</div>
<hr/>
<div class="form-horizontal">
<div class="form-group" style="padding: 0 15px 0 15px;">
<div style="display: inline;">
<label class="control-label">sharing:</label>
<div class="form-control-static" id="sharingPath">{{currentDir}}</div>
</div>
<label class="checkbox pull-right">
<input id="recursiveShareBox" type="checkbox" (change)="update()" [(ngModel)]="input.includeSubfolders"
checked="true" value="remember-me"> Include subfolders
</label>
</div>
</div>
<div class="row">
<div class="col-sm-4">
Valid:
<p id="sliderText"></p>
</div>
<div class="col-sm-4">
<input id="shareSlider" data-slider-id='shareSlider' [(ngModel)]="input.valid.amount" (change)="update()"
name="validAmount"
type="number" min="0" step="1"/>
</div>
<div class="col-sm-4">
<select class="form-control" [(ngModel)]="input.valid.type" (change)="update()" name="validType" required>
<option *ngFor="let repository of validityTypes" [value]="repository.key">{{repository.value}}
</option>
</select>
</div>
</div>
<div class="row">
<div class="col-sm-2 col-sm-push-10">
<button id="updatebutton" name="updatebutton" class="btn btn-primary">Update</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,86 @@
import {Component, OnDestroy, OnInit} from "@angular/core";
import {Utils} from "../../../../common/Utils";
import {ShareService} from "../share.service";
import {GalleryService} from "../gallery.service";
import {ContentWrapper} from "../../../../common/entities/ConentWrapper";
import {SharingDTO} from "../../../../common/entities/SharingDTO";
@Component({
selector: 'gallery-share',
templateUrl: './share.gallery.component.html',
styleUrls: ['./share.gallery.component.css'],
})
export class GalleryShareComponent implements OnInit, OnDestroy {
enabled: boolean = true;
url: string = "";
input = {
includeSubfolders: true,
valid: {
amount: 30,
type: ValidityTypes.Days
}
};
validityTypes = [];
currentDir: string = "";
sharing: SharingDTO;
contentSubscription = null;
constructor(private _sharingService: ShareService, public _galleryService: GalleryService) {
this.validityTypes = Utils.enumToArray(ValidityTypes);
}
ngOnInit() {
this.contentSubscription = this._galleryService.content.subscribe((content: ContentWrapper) => {
this.enabled = !!content.directory;
if (!this.enabled) {
return;
}
this.currentDir = Utils.concatUrls(content.directory.path, content.directory.name);
});
}
ngOnDestroy() {
if (this.contentSubscription !== null) {
this.contentSubscription.unsubscribe();
}
}
calcValidity() {
switch (parseInt(this.input.valid.type.toString())) {
case ValidityTypes.Minutes:
return this.input.valid.amount * 1000 * 60;
case ValidityTypes.Hours:
return this.input.valid.amount * 1000 * 60 * 60;
case ValidityTypes.Days:
return this.input.valid.amount * 1000 * 60 * 60 * 24;
case ValidityTypes.Months:
return this.input.valid.amount * 1000 * 60 * 60 * 24 * 30;
}
throw "unknown type: " + this.input.valid.type;
}
async update() {
this.url = "loading..";
this.sharing = await this._sharingService.updateSharing(this.currentDir, this.sharing.id, this.input.includeSubfolders, this.calcValidity());
console.log(this.sharing);
this.url = location.origin + "/share/" + this.sharing.sharingKey
}
async get() {
this.url = "loading..";
this.sharing = await this._sharingService.getSharing(this.currentDir, this.input.includeSubfolders, this.calcValidity());
console.log(this.sharing);
this.url = location.origin + "/share/" + this.sharing.sharingKey
}
}
export enum ValidityTypes{
Minutes = 0, Hours = 1, Days = 2, Months = 3
}

View File

@ -2,8 +2,6 @@ import {Component, OnInit} from "@angular/core";
import {LoginCredential} from "../../../common/entities/LoginCredential";
import {AuthenticationService} from "../model/network/authentication.service";
import {Router} from "@angular/router";
import {Message} from "../../../common/entities/Message";
import {UserDTO} from "../../../common/entities/UserDTO";
import {ErrorCodes} from "../../../common/entities/Error";
@Component({
@ -25,15 +23,17 @@ export class LoginComponent implements OnInit {
}
}
onLogin() {
async onLogin() {
this.loginError = null;
this._authService.login(this.loginCredential).then((message: Message<UserDTO>) => {
if (message.error) {
if (message.error.code === ErrorCodes.CREDENTIAL_NOT_FOUND) {
this.loginError = "Wrong username or password";
}
try {
await this._authService.login(this.loginCredential);
} catch (error) {
if (error && error.code === ErrorCodes.CREDENTIAL_NOT_FOUND) {
this.loginError = "Wrong username or password";
}
});
}
}
}

View File

@ -1,9 +1,8 @@
import {Injectable} from "@angular/core";
import {UserDTO, UserRoles} from "../../../../common/entities/UserDTO";
import {Event} from "../../../../common/event/Event";
import {BehaviorSubject} from "rxjs/BehaviorSubject";
import {UserService} from "./user.service";
import {LoginCredential} from "../../../../common/entities/LoginCredential";
import {Message} from "../../../../common/entities/Message";
import {Cookie} from "ng2-cookies";
import {ErrorCodes} from "../../../../common/entities/Error";
import {Config} from "../../../../common/config/public/Config";
@ -15,49 +14,45 @@ declare module ServerInject {
@Injectable()
export class AuthenticationService {
private _user: UserDTO = null;
public OnUserChanged: Event<UserDTO>;
public user: BehaviorSubject<UserDTO>;
constructor(private _userService: UserService) {
this.OnUserChanged = new Event();
this.user = new BehaviorSubject(null);
//picking up session..
if (this.isAuthenticated() == false && Cookie.get('pigallery2-session') != null) {
if (typeof ServerInject !== "undefined" && typeof ServerInject.user !== "undefined") {
this.setUser(ServerInject.user);
this.user.next(ServerInject.user);
}
this.getSessionUser();
} else {
this.OnUserChanged.trigger(this._user);
if (Config.Client.authenticationRequired === false) {
this.user.next(<UserDTO>{name: "", password: "", role: UserRoles.Admin});
}
}
}
private getSessionUser() {
this._userService.getSessionUser().then((message: Message<UserDTO>) => {
if (message.error) {
console.log(message.error);
} else {
this._user = message.result;
this.OnUserChanged.trigger(this._user);
}
});
private async getSessionUser(): Promise<void> {
try {
this.user.next(await this._userService.getSessionUser());
} catch (error) {
console.log(error);
}
}
private setUser(user: UserDTO) {
this._user = user;
this.OnUserChanged.trigger(this._user);
}
public login(credential: LoginCredential) {
return this._userService.login(credential).then((message: Message<UserDTO>) => {
if (message.error) {
console.log(ErrorCodes[message.error.code] + ", message: ", message.error.message);
} else {
this.setUser(message.result);
public async login(credential: LoginCredential): Promise<UserDTO> {
try {
const user = await this._userService.login(credential);
this.user.next(user);
return user;
} catch (error) {
if (typeof error.code !== "undefined") {
console.log(ErrorCodes[error.code] + ", message: ", error.message);
}
return message;
});
}
}
@ -65,19 +60,14 @@ export class AuthenticationService {
if (Config.Client.authenticationRequired === false) {
return true;
}
return !!(this._user && this._user != null);
return !!(this.user.value && this.user.value != null);
}
public getUser() {
if (Config.Client.authenticationRequired === false) {
return <UserDTO>{name: "", password: "", role: UserRoles.Admin};
}
return this._user;
}
public logout() {
this._userService.logout();
this.setUser(null);
this.user.next(null);
}

View File

@ -138,7 +138,7 @@ describe('NetworkService Fail tests', () => {
it('should call GET with error', inject([NetworkService], (networkService) => {
networkService.getJson(testUrl).then((res: Message<any>) => {
networkService.getJson(testUrl).then((res: any) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
@ -148,7 +148,7 @@ describe('NetworkService Fail tests', () => {
it('should call POST with error', inject([NetworkService, MockBackend], (networkService) => {
networkService.postJson(testUrl, testData).then((res: Message<any>) => {
networkService.postJson(testUrl, testData).then((res: any) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
@ -158,7 +158,7 @@ describe('NetworkService Fail tests', () => {
it('should call PUT with error', inject([NetworkService, MockBackend], (networkService) => {
networkService.putJson(testUrl, testData).then((res: Message<any>) => {
networkService.putJson(testUrl, testData).then((res: any) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
@ -170,7 +170,7 @@ describe('NetworkService Fail tests', () => {
it('should call DELETE with error', inject([NetworkService, MockBackend], (networkService) => {
networkService.deleteJson(testUrl).then((res: Message<any>) => {
networkService.deleteJson(testUrl).then((res: any) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);

View File

@ -3,6 +3,7 @@ import {Headers, Http, RequestOptions} from "@angular/http";
import {Message} from "../../../../common/entities/Message";
import {SlimLoadingBarService} from "ng2-slim-loading-bar";
import "rxjs/Rx";
import {ErrorCodes} from "../../../../common/entities/Error";
@Injectable()
export class NetworkService {
@ -22,9 +23,16 @@ export class NetworkService {
this.slimLoadingBarService.visible = false;
});
const process = (res: any) => {
const process = (data: any): T => {
this.slimLoadingBarService.complete();
return <Message<any>> res.json();
let res = <Message<any>> data.json();
if (!!res.error) {
if (res.error.code) {
res.error['title'] = ErrorCodes[res.error.code];
}
throw res.error;
}
return res.result;
};
const err = (err) => {
@ -62,9 +70,9 @@ export class NetworkService {
}
private static handleError(error: any) {
// TODO: in a real world app do smthing better
// TODO: in a real world app do something better
// instead of just logging it to the console
console.error(error);
return Promise.reject(error.message || error.json().error || 'Server error');
return Promise.reject(error.message || error || 'Server error');
}
}

View File

@ -2,26 +2,34 @@ import {Injectable} from "@angular/core";
import {LoginCredential} from "../../../../common/entities/LoginCredential";
import {NetworkService} from "./network.service";
import {UserDTO} from "../../../../common/entities/UserDTO";
import {Message} from "../../../../common/entities/Message";
import {Config} from "../../../../common/config/public/Config";
import {ShareService} from "../../gallery/share.service";
@Injectable()
export class UserService {
constructor(private _networkService: NetworkService) {
constructor(private _networkService: NetworkService,
private _shareService: ShareService) {
}
public logout(): Promise<Message<string>> {
public logout(): Promise<string> {
console.log("call logout");
return this._networkService.postJson("/user/logout");
}
public login(credential: LoginCredential): Promise<Message<UserDTO>> {
public login(credential: LoginCredential): Promise<UserDTO> {
return this._networkService.postJson("/user/login", {"loginCredential": credential});
}
public getSessionUser(): Promise<Message<UserDTO>> {
return this._networkService.getJson("/user/login");
public async getSessionUser(): Promise<UserDTO> {
await this._shareService.wait();
if (Config.Client.Sharing.enabled == true) {
if (this._shareService.isSharing()) {
return this._networkService.getJson<UserDTO>("/user/login?sk=" + this._shareService.getSharingKey());
}
}
return this._networkService.getJson<UserDTO>("/user/login");
}
}

View File

@ -3,7 +3,6 @@ import {AuthenticationService} from "../../model/network/authentication.service"
import {Router} from "@angular/router";
import {UserDTO, UserRoles} from "../../../../common/entities/UserDTO";
import {Utils} from "../../../../common/Utils";
import {Message} from "../../../../common/entities/Message";
import {UserManagerSettingsService} from "./usermanager.settings.service";
@Component({
@ -22,23 +21,21 @@ export class UserMangerSettingsComponent implements OnInit {
}
ngOnInit() {
if (!this._authService.isAuthenticated() || this._authService.getUser().role < UserRoles.Admin) {
if (!this._authService.isAuthenticated() || this._authService.user.value.role < UserRoles.Admin) {
this._router.navigate(['login']);
return;
}
this.userRoles = Utils.enumToArray(UserRoles).filter(r => r.key <= this._authService.getUser().role);
this.userRoles = Utils.enumToArray(UserRoles).filter(r => r.key <= this._authService.user.value.role);
this.getUsersList();
}
private getUsersList() {
this._userSettings.getUsers().then((result: Message<Array<UserDTO>>) => {
this.users = result.result;
});
private async getUsersList() {
this.users = await this._userSettings.getUsers();
}
canModifyUser(user: UserDTO): boolean {
let currentUser = this._authService.getUser();
let currentUser = this._authService.user.value;
if (!currentUser) {
return false;
}

View File

@ -1,7 +1,6 @@
import {Injectable} from "@angular/core";
import {UserDTO} from "../../../../common/entities/UserDTO";
import {NetworkService} from "../../model/network/network.service";
import {Message} from "../../../../common/entities/Message";
@Injectable()
export class UserManagerSettingsService {
@ -10,21 +9,21 @@ export class UserManagerSettingsService {
constructor(private _networkService: NetworkService) {
}
public createUser(user: UserDTO): Promise<Message<string>> {
public createUser(user: UserDTO): Promise<string> {
return this._networkService.putJson("/user", {newUser: user});
}
public getUsers(): Promise<Message<Array<UserDTO>>> {
public getUsers(): Promise<Array<UserDTO>> {
return this._networkService.getJson("/user/list");
}
public deleteUser(user: UserDTO) {
public deleteUser(user: UserDTO): Promise<void> {
return this._networkService.deleteJson("/user/" + user.id);
}
public updateRole(user: UserDTO) {
public updateRole(user: UserDTO): Promise<void> {
return this._networkService.postJson("/user/" + user.id + "/role", {newRole: user.role});
}
}

View File

@ -0,0 +1,3 @@
body {
background: #eee;
}

View File

@ -0,0 +1,15 @@
<div class="container">
<div class="col-sm-offset-3 col-sm-6 col-lg-4 col-lg-offset-4">
<form class="form-signin" #LoginForm="ngForm">
<h2 class="form-signin-heading">The link is password protected</h2>
<div *ngIf="loginError">
{{loginError}}
</div>
<input type="password" class="form-control" placeholder="Password"
[(ngModel)]="loginCredential.password" name="password" required>
<br/>
<button class="btn btn-lg btn-primary btn-block" [disabled]="!LoginForm.form.valid" (click)="onLogin()">OK
</button>
</form>
</div>
</div> <!-- /container -->

View File

@ -0,0 +1,39 @@
import {Component, OnInit} from "@angular/core";
import {LoginCredential} from "../../../common/entities/LoginCredential";
import {AuthenticationService} from "../model/network/authentication.service";
import {Router} from "@angular/router";
import {ErrorCodes} from "../../../common/entities/Error";
@Component({
selector: 'share-login',
templateUrl: './share-login.component.html',
styleUrls: ['./share-login.component.css'],
})
export class ShareLoginComponent implements OnInit {
loginCredential: LoginCredential;
loginError: any = null;
constructor(private _authService: AuthenticationService, private _router: Router) {
this.loginCredential = new LoginCredential();
}
ngOnInit() {
if (this._authService.isAuthenticated()) {
this._router.navigate(['gallery', "/"]);
}
}
async onLogin() {
this.loginError = null;
try {
await this._authService.login(this.loginCredential);
} catch (error) {
if (error && error.code === ErrorCodes.CREDENTIAL_NOT_FOUND) {
this.loginError = "Wrong username or password";
}
}
}
}

View File

@ -6,6 +6,7 @@
"homepage": "https://github.com/bpatrik/PiGallery2",
"license": "MIT",
"main": "./backend/server.js",
"bin": "./backend/server.js",
"scripts": {
"build": "ng build",
"pretest": "tsc",
@ -23,50 +24,48 @@
"url": "https://github.com/bpatrik/PiGallery2/issues"
},
"dependencies": {
"@agm/core": "^1.0.0-beta.0",
"@angular/common": "~4.2.4",
"@angular/compiler": "~4.2.4",
"@angular/core": "~4.2.4",
"@angular/forms": "~4.2.4",
"@angular/http": "~4.2.4",
"@angular/platform-browser": "~4.2.4",
"@angular/platform-browser-dynamic": "~4.2.4",
"@angular/router": "~4.2.4",
"body-parser": "^1.17.2",
"core-js": "^2.4.1",
"ejs": "^2.5.6",
"exif-parser": "^0.1.9",
"express": "^4.15.3",
"express-session": "^1.15.3",
"flat-file-db": "^1.0.0",
"intl": "^1.2.5",
"jimp": "^0.2.28",
"mime": "^1.3.6",
"mocha": "^3.4.2",
"mysql": "^2.13.0",
"ng2-cookies": "^1.0.12",
"ng2-slim-loading-bar": "^4.0.0",
"node-iptc": "^1.0.4",
"reflect-metadata": "^0.1.10",
"rxjs": "^5.4.1",
"systemjs": "0.20.14",
"threads": "^0.8.1",
"typeconfig": "^1.0.1",
"typeorm": "0.0.11",
"winston": "^2.3.1",
"zone.js": "^0.8.12"
"winston": "^2.3.1"
},
"devDependencies": {
"@angular/cli": "1.1.3",
"@angular/compiler-cli": "^4.2.4",
"@angular/language-service": "^4.2.4",
"@angular/cli": "1.2.0",
"@angular/compiler-cli": "^4.2.5",
"@angular/language-service": "^4.2.5",
"@types/express": "^4.0.36",
"@types/express-session": "1.15.0",
"@types/gm": "^1.17.31",
"@types/jasmine": "^2.5.53",
"@types/node": "^8.0.4",
"@types/node": "^8.0.7",
"@types/sharp": "^0.17.2",
"ng2-cookies": "^1.0.12",
"ng2-slim-loading-bar": "^4.0.0",
"intl": "^1.2.5",
"core-js": "^2.4.1",
"zone.js": "^0.8.12",
"rxjs": "^5.4.1",
"@types/winston": "^2.3.3",
"@agm/core": "^1.0.0-beta.0",
"@angular/common": "~4.2.5",
"@angular/compiler": "~4.2.5",
"@angular/core": "~4.2.5",
"@angular/forms": "~4.2.5",
"@angular/http": "~4.2.5",
"@angular/platform-browser": "~4.2.5",
"@angular/platform-browser-dynamic": "~4.2.5",
"@angular/router": "~4.2.5",
"chai": "^4.0.2",
"codelyzer": "~3.1.1",
"ejs-loader": "^0.3.0",
@ -83,20 +82,20 @@
"karma-phantomjs-launcher": "^1.0.4",
"karma-remap-istanbul": "^0.6.0",
"karma-systemjs": "^0.16.0",
"merge2": "^1.0.3",
"merge2": "^1.1.0",
"mocha": "^3.4.2",
"phantomjs-prebuilt": "^2.1.14",
"protractor": "^5.1.2",
"remap-istanbul": "^0.9.5",
"rimraf": "^2.6.1",
"run-sequence": "^1.2.2",
"run-sequence": "^2.0.0",
"ts-helpers": "^1.1.2",
"ts-node": "~3.1.0",
"tslint": "^5.4.3",
"typescript": "^2.3.4"
"typescript": "^2.4.1"
},
"optionalDependencies": {
"gm": "^1.23.0",
"sharp": "^0.18.1"
"sharp": "^0.18.2"
}
}

View File

@ -9,222 +9,222 @@ import {Config} from "../../../../../common/config/private/Config";
describe('Authentication middleware', () => {
beforeEach(() => {
ObjectManagerRepository.reset();
});
describe('authenticate', () => {
it('should call next on authenticated', (done) => {
let req: any = {
session: {
user: "A user"
}
};
let next: any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.authenticate(req, null, next);
});
it('should call next with error on not authenticated', (done) => {
let req: any = {
session: {}
};
Config.Client.authenticationRequired = true;
let res: any = {};
let next: any = (err: Error) => {
expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.NOT_AUTHENTICATED);
done();
};
AuthenticationMWs.authenticate(req, null, next);
});
});
describe('inverseAuthenticate', () => {
it('should call next with error on authenticated', (done) => {
let req: any = {
session: {}
};
let res: any = {};
let next: any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.inverseAuthenticate(req, null, next);
});
it('should call next error on authenticated', (done) => {
let req: any = {
session: {
user: "A user"
}
};
let res: any = {};
let next: any = (err: Error) => {
expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.ALREADY_AUTHENTICATED);
done();
};
AuthenticationMWs.inverseAuthenticate(req, null, next);
});
});
describe('authorise', () => {
it('should call next on authorised', (done) => {
let req: any = {
session: {
user: {
role: UserRoles.Guest
}
}
};
let next: any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.authorise(UserRoles.Guest)(req, null, next);
});
it('should call next with error on not authorised', (done) => {
let req: any = {
session: {
user: {
role: UserRoles.Guest
}
}
};
let next: any = (err: Error) => {
expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.NOT_AUTHORISED);
done();
};
AuthenticationMWs.authorise(UserRoles.Developer)(req, null, next);
});
});
describe('login', () => {
beforeEach(() => {
ObjectManagerRepository.reset();
ObjectManagerRepository.reset();
});
describe('authenticate', () => {
it('should call next on authenticated', (done) => {
let req:any = {
session: {
user: "A user"
}
};
let next:any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.authenticate(req, null, next);
describe('should call next on missing...', () => {
it('body', (done) => {
let req: any = {};
let next: any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.login(req, null, next);
});
});
it('should call next with error on not authenticated', (done) => {
let req:any = {
session: {}
};
Config.Client.authenticationRequired = true;
let res:any = {};
let next:any = (err:Error) => {
expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.NOT_AUTHENTICATED);
done();
};
AuthenticationMWs.authenticate(req, null, next);
});
});
describe('inverseAuthenticate', () => {
it('should call next with error on authenticated', (done) => {
let req:any = {
session: {}
};
let res:any = {};
let next:any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.inverseAuthenticate(req, null, next);
});
it('loginCredential', (done) => {
let req: any = {
body: {}
};
let next: any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.login(req, null, next);
it('should call next error on authenticated', (done) => {
let req:any = {
session: {
user: "A user"
}
};
let res:any = {};
let next:any = (err:Error) => {
expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.ALREADY_AUTHENTICATED);
done();
};
AuthenticationMWs.inverseAuthenticate(req, null, next);
});
});
describe('authorise', () => {
it('should call next on authorised', (done) => {
let req:any = {
session: {
user: {
role: UserRoles.Guest
}
}
};
let next:any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.authorise(UserRoles.Guest)(req, null, next);
});
it('should call next with error on not authorised', (done) => {
let req:any = {
session: {
user: {
role: UserRoles.Guest
}
}
};
let next:any = (err:Error) => {
expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.NOT_AUTHORISED);
done();
};
AuthenticationMWs.authorise(UserRoles.Developer)(req, null, next);
});
});
describe('login', () => {
beforeEach(() => {
ObjectManagerRepository.reset();
});
describe('should call next on missing...', () => {
it('body', (done) => {
let req:any = {};
let next:any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.login(req, null, next);
});
it('loginCredential', (done) => {
let req:any = {
body: {}
};
let next:any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.login(req, null, next);
});
});
it('loginCredential content', (done) => {
let req: any = {
body: {loginCredential: {}}
};
let next: any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.login(req, null, next);
it('loginCredential content', (done) => {
let req:any = {
body: {loginCredential: {}}
};
let next:any = (err) => {
expect(err).to.be.undefined;
done();
};
AuthenticationMWs.login(req, null, next);
});
});
it('should call next with error on not finding user', (done) => {
let req:any = {
body: {
loginCredential: {
username: "aa",
password: "bb"
}
}
};
let next:any = (err:Error) => {
expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.CREDENTIAL_NOT_FOUND);
done();
};
ObjectManagerRepository.getInstance().setUserManager(<UserManager>{
findOne: (filter, cb) => {
cb(null, null);
}
});
AuthenticationMWs.login(req, null, next);
});
it('should call next with user on the session on finding user', (done) => {
let req:any = {
session: {},
body: {
loginCredential: {
username: "aa",
password: "bb"
}
}
};
let next:any = (err:Error) => {
expect(err).to.be.undefined;
expect(req.session.user).to.be.eql("test user");
done();
};
ObjectManagerRepository.getInstance().setUserManager(<UserManager>{
findOne: (filter, cb:any) => {
cb(null, "test user");
}
});
AuthenticationMWs.login(req, null, next);
});
});
describe('logout', () => {
it('should call next on logout', (done) => {
let req:any = {
session: {
user: {
role: UserRoles.Guest
}
}
};
let next:any = (err) => {
expect(err).to.be.undefined;
expect(req.session.user).to.be.undefined;
done();
};
AuthenticationMWs.logout(req, null, next);
});
});
});
it('should call next with error on not finding user', (done) => {
let req: any = {
body: {
loginCredential: {
username: "aa",
password: "bb"
}
}
};
let next: any = (err: Error) => {
expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.CREDENTIAL_NOT_FOUND);
done();
};
ObjectManagerRepository.getInstance().UserManager = <UserManager>{
findOne: (filter, cb) => {
cb(null, null);
}
};
AuthenticationMWs.login(req, null, next);
});
it('should call next with user on the session on finding user', (done) => {
let req: any = {
session: {},
body: {
loginCredential: {
username: "aa",
password: "bb"
}
}
};
let next: any = (err: Error) => {
expect(err).to.be.undefined;
expect(req.session.user).to.be.eql("test user");
done();
};
ObjectManagerRepository.getInstance().UserManager = <UserManager>{
findOne: (filter, cb: any) => {
cb(null, "test user");
}
};
AuthenticationMWs.login(req, null, next);
});
});
describe('logout', () => {
it('should call next on logout', (done) => {
let req: any = {
session: {
user: {
role: UserRoles.Guest
}
}
};
let next: any = (err) => {
expect(err).to.be.undefined;
expect(req.session.user).to.be.undefined;
done();
};
AuthenticationMWs.logout(req, null, next);
});
});
});

View File

@ -1,6 +1,7 @@
{
"compileOnSave": true,
"compilerOptions": {
"skipLibCheck": true,
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",