Skip to content

Commit

Permalink
Merge pull request #3 from neryams/0.1.1
Browse files Browse the repository at this point in the history
0.1.1 Release
  • Loading branch information
neryams committed Jan 18, 2019
2 parents 2c945ba + 8b147ff commit 2d3d66c
Show file tree
Hide file tree
Showing 36 changed files with 2,833 additions and 1,375 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
# compiled output
/.cache
*/dist
*/uploads/**
uploads
/docs

# dependencies
node_modules/
*/node_modules/
mongodb/

Expand Down
8 changes: 5 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"request": "launch",
"name": "Launch app.ts",
"protocol": "inspector",
"env": {"NODE_CONFIG_DIR": "../config"},
"args": ["${workspaceFolder}/nxgallery-api/app.ts"],
"cwd": "${workspaceFolder}/nxgallery-api",
"runtimeArgs": ["-r", "${workspaceFolder}/nxgallery-api/node_modules/ts-node/register"],
Expand All @@ -17,10 +18,11 @@
{
"type": "node",
"request": "launch",
"name": "Launch current file w/ ts-node",
"name": "Launch PROD app.ts",
"protocol": "inspector",
"args": ["${relativeFile}"],
"cwd": "${workspaceFolder}",
"env": {"NODE_ENV": "production", "NODE_CONFIG_DIR": "../config"},
"args": ["${workspaceFolder}/nxgallery-api/app.ts"],
"cwd": "${workspaceFolder}/nxgallery-api",
"runtimeArgs": ["-r", "${workspaceFolder}/nxgallery-api/node_modules/ts-node/register"],
"internalConsoleOptions": "openOnSessionStart"
}
Expand Down
89 changes: 51 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# NXGallery
NXGallery is a next-generation gallery manager. It comes with a server module to resize image thumbnails and maintain the database, and a front end module to serve the images and offer a management dashboard.

Demo is accessible at https://demo.nxgallery.com/
Dashboard login credentials for testing uploads are `test` / `password`; the database will be cleared regularly.

## System Requirements
- Node.js (Tested with node version 8.9.3)
- Yarn (or NPM)
Expand All @@ -9,53 +12,63 @@ NXGallery is a next-generation gallery manager. It comes with a server module to
## To Build for deployment
1. Clone or download the respository
2. Set up configuration options inside `./config/env.ts`
`API_PORT` is the port the server will be running on
`CLIENT_PORT` is the port the angular client will be served on (when running the compiled version; the dev build follows the rules in angular.json)
`MONGODB_PORT` is the port your MongoDB is running on (default is 27017)
`LOCAL_STORAGE` is where images will be uploaded relative to the api server code. This path should be publicly accessible.
`LOCAL_ORIGINAL_STORAGE` is where the original images will be uploaded relative to the api server code (for later retrieval, potentially printing, making new thumbnails, etc). **This path should not be publicly accessible**
`LOCAL_BASE_URL` is the public path that will be used to access the image thumbnails (the public path from d.)
`MAX_UPLOAD_SIZE` is the maximum number of megabytes an uploaded image can be
`IMAGE_SIZES` is an array of sizes that thumbnails will be generated for. This array can be any length, and the generated image paths will be stored in the database.
`JWT_SECRET` is **your** secret key for generating JWT tokens for user login into the maangement panel. You should generate a random hexadecimal UUID using https://www.random.org/bytes/ or something similar and use that.
3. Navigate to `./nxgallery-api` and run the following:
```sh
$ yarn install
$ yarn build
$ node dist/nxgallery-api/app
```
4. Navigate to `./nxgallery-ui` and run
```sh
$ yarn install
$ yarn build:ssr
$ node dist/nxgallery-ui/server
```
5. Use Postman to set up the administrator account. Make a `POST` request to `localhost:<API_PORT>/api/users/setup` with the following payload.
```json
{
"username": "test",
"password": "password"
}
```
Once the administator account is created, the setup route is disabled and will only return an error message. Currently to reset the account or password the `users` collection needs to be dropped. The password is SHA512 hashed with a salt in the database.
`API_PORT` is the port the server will be running on
`CLIENT_PORT` is the port the angular client will be served on (when running the compiled version; the dev build follows the rules in angular.json)
`MONGODB_PORT` is the port your MongoDB is running on (default is 27017)
`LOCAL_STORAGE` is where images will be uploaded relative to the api server code. This path should be publicly accessible. By default the Express api server will expose `LOCAL_STORAGE` at `/images/`.
`LOCAL_ORIGINAL_STORAGE` is where the original images will be uploaded relative to the api server code (for later retrieval, potentially printing, making new thumbnails, etc). **This path should not be publicly accessible!**
`LOCAL_UPLOADS_BASE_URL` is the absolute public path that will be used to access the image thumbnails (the public path corresponding to `LOCAL_STORAGE`)
`MAX_UPLOAD_SIZE` is the maximum number of megabytes an uploaded image can be
`IMAGE_SIZES` is an array of sizes that thumbnails will be generated for. This array can be any length, and the generated image paths will be stored in the database.
`JWT_SECRET` is **your** secret key for generating JWT tokens for user login into the maangement panel. You should generate a random hexadecimal UUID using https://www.random.org/bytes/ or something similar and use that.
3. In the repository root, run the following:
```sh
$ yarn install
$ yarn build:prod
$ yarn run:prod
```
Note: You can use `build` instead of `build:prod` for testing, as it builds a lot faster; but the latter command will build a smaller application.
4. Use Postman to set up the administrator account. Make a `POST` request to `<IP_ADDRESS>:<API_PORT>/api/users/setup` with the following raw JSON payload.
```json
{
"username": "test",
"password": "password"
}
```
Once the administator account is created, the setup route is disabled and will only return an error message. Currently to reset the account or password the `users` collection needs to be dropped. The password is SHA512 hashed with a salt in the database.

You can run the build commands above locally, then copy the compiled `dist` folders from both projects folders to your server. That will allow you to run the `node dist` commands directly on the server to host the applications without needing to rebuild. NGINX can be used to forward the requests to the nxgallery-ui server.
In this case Yarn will not be required on the server - you will only need Node.js and MongoDB.
## Deploying to production
You can run the `yarn install` and `yarn build` commands above locally, the run the following command from the same directory as this README to copy the necessary files to your server:
```sh
rsync -avP -e "ssh -p <SSH_PORT (default 22)>" --relative ./ --filter="merge rsync" <REMOTE_USERNAME>@<REMOTE_HOST>:~/<REMOTE_TARGET_DIRECTORY>
```
That way you can avoid performing the typescript and angular compilations on the server.
Then on the server, just run
```sh
yarn install --production
yarn run:prod
```
NODE_ENV should be set to production. Running `yarn run:prod` will set it automatically for that local script but you will need to set it manually if the apps are run manually; otherwise the configuration files will be loaded incorrectly.

## For development
1. Fork and clone the repository
2. Set up environments config as in deployment
3. `yarn install` in both directories
4. `yarn start` in `nxgallery-api` to run server
5. `yarn start` in `nxgallery-ui` to run the client
Note: If you change the `API_PORT` config, you need to update the path to the local development api server for the dev proxy inside `nxgallery-ui/proxy/proxy.conf.json`
4. `yarn start` (`yarn start:prod` for testing production settings) in `nxgallery-api` to run server
5. `yarn start` (or `yarn start:prod`) in `nxgallery-ui` to run the client
Note: If you change the `API_PORT` config, you need to update the path to the local development api server for the dev proxy inside `nxgallery-ui/proxy/proxy.conf.json`

* To run unit tests, run `yarn test`
* To run e2e tests, run `yarn e2e`

## New Features to add!
## New Features to add! (rough order of priority)
- Speed up image conversion, try https://github.com/lovell/sharp
- Responsive thumbnail resolution
- Dragging images to reorder in dashboard
- Optionally integrate with AWS, Google Cloud, Azure, etc, adding platform deployment keys and info to the config
- Add tags to image database with fulltext search
- Fullscreen lightbox on view
- Add captions to images
- Add album functionality, album ids to images
- Responsive thumbnail resolution
- Fullscreen lightbox on view
- Optionally integrate with AWS, Google Cloud, Azure, etc, adding platform deployment keys and info to the config
- Setup UI allowing user to set mongodb information, environment variables, and admin account
- Horizontal gallery option
22 changes: 22 additions & 0 deletions config/default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const API_PORT = 16906;

export default {
API_PORT: API_PORT,
CLIENT_PORT: 4000,
MONGODB_PORT: 27017,

// Relative to the cwd path.
// Prefixing with a ~ will make the path relative to the project root (the parent folder of this config file's folder)
LOCAL_STORAGE: '~/uploads/images',
LOCAL_ORIGINAL_STORAGE: '~/uploads/original',

LOCAL_UPLOADS_BASE_URL: `\/\/localhost:${API_PORT}/images/`,
MAX_UPLOAD_SIZE: 20 * 1024 * 1024,
IMAGE_SIZES: [
300,
600,
1200
],

JWT_SECRET: 'wow very secret, much secure' // Change this (wow)
}
15 changes: 0 additions & 15 deletions config/env.ts

This file was deleted.

4 changes: 4 additions & 0 deletions config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import defaultConfig from './default';
import prodConfig from './production';

export { defaultConfig, prodConfig };
5 changes: 5 additions & 0 deletions config/production.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import defaultConfig from './default';

export default {
LOCAL_UPLOADS_BASE_URL: `\/\/localhost:${defaultConfig.CLIENT_PORT}/images/`
};
6 changes: 4 additions & 2 deletions nxgallery-api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import * as express from 'express';
import { join } from 'path';
import * as cookieParser from 'cookie-parser';
import * as logger from 'morgan';
import { LOCAL_STORAGE, JWT_SECRET } from '../config/env';
import * as config from 'config';
import { BASE_DIR, getAbsolutePath } from './helpers/PathFixer';

import { imageRouter } from './routes/image.routes';
import { usersRouter } from './routes/users.routes';
Expand All @@ -19,7 +20,8 @@ app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(join(__dirname, 'dist')));
app.use('/images', express.static(join(__dirname, LOCAL_STORAGE)));

app.use('/images', express.static(getAbsolutePath(config.get('LOCAL_STORAGE'))));

app.use('/api/image', imageRouter);
app.use('/api/users', usersRouter);
Expand Down
6 changes: 3 additions & 3 deletions nxgallery-api/controllers/image.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { Document } from 'mongoose';
import { ImageDatabase } from './../models/image.model';
import { FileStreamOutputResult } from './../helpers/ImageStorage';
import { Request, Response } from 'express';
import * as config from 'config';

import * as _ from 'lodash';
import * as multer from 'multer';
import { resolve } from 'url';

import { ImageStorage } from '../helpers/ImageStorage';
import { MAX_UPLOAD_SIZE, IMAGE_SIZES } from './../../config/env';
import { ImageData } from './../../shared';

// setup a new instance of the AvatarStorage engine
Expand All @@ -20,7 +20,7 @@ const storage = new ImageStorage({

const limits = {
files: 1, // allow only 1 file per request
fileSize: MAX_UPLOAD_SIZE,
fileSize: config.get('MAX_UPLOAD_SIZE'),
};

// setup multer
Expand Down Expand Up @@ -75,7 +75,7 @@ export class ImageController {
const matches: string[] = filename.match(/^(.+?)_.+?\.(.+)$/i);

if (matches && matches.length > 0) {
_.each(IMAGE_SIZES, (size) => {
_.each(config.get('IMAGE_SIZES'), (size) => {
let sizeFilename = matches[1] + '_' + size + '.' + matches[2];
let url = resolve(file.baseUrl, sizeFilename);

Expand Down
4 changes: 2 additions & 2 deletions nxgallery-api/controllers/users.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Request, Response } from 'express';
import * as jwt from 'jsonwebtoken';
import * as config from 'config';

import { UsersDatabase } from './../models/users.model';
import { User, ErrorCodes } from './../../shared';
import { JWT_SECRET } from './../../config/env';

export interface UserAuthInput {
username: string,
Expand Down Expand Up @@ -45,7 +45,7 @@ export class UsersController {
authenticate(req: Request, res: Response) {
const body: UserAuthInput = req.body;
this.usersDatabase.authenticate(body).then((response: User) => {
let token = jwt.sign(response, JWT_SECRET, { expiresIn: '7d' });
let token = jwt.sign(response, config.get('JWT_SECRET'), { expiresIn: '7d' });
res.json({ token });
}, (err) => {
if (err.message === ErrorCodes.notAuthenticated.code) {
Expand Down
20 changes: 9 additions & 11 deletions nxgallery-api/helpers/ImageStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import * as streamifier from 'streamifier';
import { pseudoRandomBytes, createHash } from 'crypto';
import { existsSync, createWriteStream, unlink, WriteStream } from 'fs';
import { resolve, join, basename } from 'path';
import * as config from 'config';

import { LOCAL_STORAGE, LOCAL_BASE_URL, IMAGE_SIZES, LOCAL_ORIGINAL_STORAGE } from '../../config/env';
import { ImageInfo } from '../../shared';

export const UPLOAD_PATH = resolve(__dirname, '..', LOCAL_STORAGE);
export const UPLOAD_PATH_ORIGNAL = resolve(__dirname, '..', LOCAL_ORIGINAL_STORAGE);
import { BASE_DIR, getAbsolutePath } from './PathFixer';

export interface FileStreamOutputResult {
destination: string;
Expand Down Expand Up @@ -80,9 +78,9 @@ export class ImageStorage implements multer.StorageEngine {
}
});

// set the upload path
this.uploadPath = UPLOAD_PATH;
this.uploadPathOriginal = UPLOAD_PATH_ORIGNAL;
// set the upload paths
this.uploadPath = getAbsolutePath(config.get('LOCAL_STORAGE'));
this.uploadPathOriginal = getAbsolutePath(config.get('LOCAL_ORIGINAL_STORAGE'));

if (this.options.storage == 'local') {
// if upload path does not exist, create the upload path structure
Expand All @@ -91,7 +89,7 @@ export class ImageStorage implements multer.StorageEngine {
}
}

_handleFile(req: Express.Request, file: any, cb: (error?: any, info?: Partial<Express.Multer.File>) => void) {
_handleFile(req: Express.Request, file: any, cb: (error?: any, info?: Partial<FileStreamOutputResult>) => void) {
// create a writable stream using concat-stream that will
// concatenate all the buffers written to it and pass the
// complete buffer to a callback fn
Expand Down Expand Up @@ -128,7 +126,7 @@ export class ImageStorage implements multer.StorageEngine {
matches = !!pathsplit.pop().match(/^(.+?)_.+?\.(.+)$/i);

if (matches) {
paths = _.map(IMAGE_SIZES, function(size) {
paths = _.map(config.get('IMAGE_SIZES'), function(size) {
return pathsplit.join('/') + '/' + (matches[1] + '_' + size + '.' + matches[2]);
});
}
Expand Down Expand Up @@ -173,7 +171,7 @@ export class ImageStorage implements multer.StorageEngine {
output.on('finish', () => {
cb(null, {
destination: this.uploadPath,
baseUrl: LOCAL_BASE_URL,
baseUrl: config.get('LOCAL_UPLOADS_BASE_URL'),
filename: basename(filepath),
storage: this.options.storage,
imageInfo: imageInfo
Expand Down Expand Up @@ -248,7 +246,7 @@ export class ImageStorage implements multer.StorageEngine {

let filepathParts = filename.split('.');
// map through the responsive sizes and push them to the batch
batch = _.map(IMAGE_SIZES, (size) => {
batch = _.map(config.get('IMAGE_SIZES'), (size) => {
let image: Jimp = null;

// create the complete filepath and create a writable stream for it
Expand Down
10 changes: 10 additions & 0 deletions nxgallery-api/helpers/PathFixer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { join } from 'path';

export const BASE_DIR = join(__dirname, '..', '..');
export function getAbsolutePath(path: string): string {
if (path.substr(0,1) === '~') {
return path.replace('~', BASE_DIR);
} else {
return join(process.cwd(), path);
}
}
7 changes: 3 additions & 4 deletions nxgallery-api/models/base.model.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { MONGODB_PORT } from './../../config/env';
import { connect } from 'mongoose';

connect('mongodb://localhost' + (MONGODB_PORT ? `:${MONGODB_PORT}` : '') + '/nxgallery', { useNewUrlParser: true }).then(() => {
import * as config from 'config';

connect('mongodb://localhost' + (config.get('MONGODB_PORT') ? `:${config.get('MONGODB_PORT')}` : '') + '/nxgallery', { useNewUrlParser: true }).then(() => {
console.log('Connection Successful');
}, (err: any) => {
console.error('Connection Failed: ', err);
});

export class BaseDatabase {
constructor() {

}
}
Loading

0 comments on commit 2d3d66c

Please sign in to comment.