Skip to content
This repository has been archived by the owner on Jul 28, 2019. It is now read-only.

Commit

Permalink
feat(Server): implement gathering & serving of tags
Browse files Browse the repository at this point in the history
  • Loading branch information
Emanuel Kluge committed Oct 9, 2017
1 parent 6fe7e96 commit 3ade421
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 5 deletions.
11 changes: 11 additions & 0 deletions lib/definitions/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface PhotoMeta {
iso: number | null;
lens: string;
orientation: Orientation;
tags: string[];
title: string;
width: number;
}
Expand Down Expand Up @@ -119,6 +120,10 @@ export interface Album {
images: Image[];
}

export interface Tags {
[x: string]: Image[];
}

export interface FrontpageAlbum {
meta: SomeObject;
linkProps: LinkProps;
Expand All @@ -127,6 +132,7 @@ export interface FrontpageAlbum {
export interface Data {
albums: Album[];
pages: Page[];
tags: Tags;
}

export interface FrontpageApiData {
Expand All @@ -143,6 +149,11 @@ export interface ImageApiData {

export type AlbumApiData = Album;

export interface TagApiData {
title: string;
images: Image[];
}

export type PageApiData = Page;

export interface ExportPathMap {
Expand Down
110 changes: 106 additions & 4 deletions lib/get-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,33 @@ interface PageCache {
}

interface ImageCache {
[pattern: string]: Image;
[filePath: string]: Image;
}

interface TagCache {
[name: string]: Image[];
}

let pageCache: PageCache = {};
let imageCache: ImageCache = {};
let tagCache: TagCache = {};

const getDirName = (pathName: string): string =>
path
.dirname(pathName)
.split(path.sep)
.pop();

const sortTagImages = (images: Image[]): Image[] =>
images.sort((a: Image, b: Image): number =>
a.meta.title.localeCompare(b.meta.title)
);

const sortTagCacheAlphabetically = (t: TagCache): TagCache =>
Object.keys(t)
.sort(util.sortAlphabetically)
.reduceRight((a, b) => ({ [b]: sortTagImages(t[b]), ...a }), {});

const removeFromCache = (cacheType: 'page' | 'image') => (
filePath: string
): void => {
Expand Down Expand Up @@ -65,6 +80,68 @@ const removeFromCache = (cacheType: 'page' | 'image') => (
}
};

const addImageToTagCache = (image: Image): TagCache =>
image.meta.tags.reduce(
(acc: TagCache, tag: string): TagCache => {
if (!Boolean(acc[tag])) {
acc[tag] = [image];
return acc;
}

const imageIndex = acc[tag].findIndex(
(img: Image): boolean => img.filePath === image.filePath
);

if (imageIndex === -1) {
acc[tag] = acc[tag].concat(image);
} else {
acc[tag] = acc[tag]
.slice(0, imageIndex)
.concat([image].concat(acc[tag].slice(imageIndex + 1)));
}
return acc;
},
{ ...tagCache }
);

const removeImageFromTagCache = (filePath: string): void => {
if (!filePath.endsWith('.jpg')) {
return;
}
const relativeFilePath = util.stripSlashes(
filePath.replace(process.cwd(), '')
);

tagCache = sortTagCacheAlphabetically(
Object.keys(tagCache).reduce(
(acc: TagCache, tag: string): TagCache => {
const imageIndex = tagCache[tag].findIndex(
(img: Image): boolean =>
util.stripSlashes(img.filePath) === relativeFilePath
);

if (imageIndex === -1) {
return acc;
}

const images = tagCache[tag]
.slice(0, imageIndex)
.concat(acc[tag].slice(imageIndex + 1));

if (!images.length) {
return acc;
}

return {
...acc,
[tag]: images,
};
},
{ ...tagCache }
)
);
};

const getDataForImage = (albumsDir: string, albumName: string) => (
file: string
): Image => ({
Expand All @@ -80,10 +157,12 @@ const getMetaFromImage = async (image: Image): Promise<Image> => {
}

const meta = await getImageMeta(image.filePath);
const imageWithMeta = { ...image, meta };
imageCache = Object.assign({}, imageCache, {
[strippedImagePath]: { ...image, meta },
[strippedImagePath]: imageWithMeta,
});
return imageCache[strippedImagePath];
tagCache = sortTagCacheAlphabetically(addImageToTagCache(imageWithMeta));
return imageWithMeta;
};

export const getImages = async (
Expand Down Expand Up @@ -151,6 +230,28 @@ export const initCachePurger = (config: Config): void => {
pagesMonitor.on('unlink', removeFromCache('page'));
imagesMonitor.on('change', removeFromCache('image'));
imagesMonitor.on('unlink', removeFromCache('image'));
imagesMonitor.on('change', removeImageFromTagCache);
imagesMonitor.on('unlink', removeImageFromTagCache);
};

export const getImagesforTag = async (
config: Config,
tag: string
): Promise<Image[]> => {
if (Boolean(tagCache[tag])) {
return tagCache[tag];
}

const { albums } = util.getGlobPatterns(config);
const albumFiles = await globby(albums);
await Promise.all(
albumFiles.map(async (albumFile: string): Promise<void> => {
const albumName = getDirName(albumFile);
await getImages(config.albumsDir, albumName);
})
);

return tagCache[tag] || [];
};

export default async (config: Config): Promise<Data> => {
Expand All @@ -162,5 +263,6 @@ export default async (config: Config): Promise<Data> => {
patterns.albums,
config.contentDir
);
return { albums, pages };
const tags = tagCache;
return { albums, pages, tags };
};
3 changes: 2 additions & 1 deletion lib/get-image-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as marked from 'marked';
import nodeIptc = require('node-iptc');
import getImageSize = require('probe-image-size');
import { decode } from 'utf8';
import { pReadFile } from './util';
import { pReadFile, sortAlphabetically } from './util';
import {
GPS,
LatLng,
Expand Down Expand Up @@ -103,6 +103,7 @@ const getDetailsFromMeta = (
): PhotoMeta => ({
...dimensions,
title: decode(iptc.object_name || ''),
tags: (iptc.keywords || []).map(decode).sort(sortAlphabetically),
description: marked(decode(iptc.caption || '')),
createdAt: iptc.date_created
? getCreationDateFromString(iptc.date_created)
Expand Down
8 changes: 8 additions & 0 deletions lib/handlers/tag-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getImagesforTag } from '../get-data';
import { RequestHandler, Config } from '../definitions/global';

export default (config: Config): RequestHandler => async (req, res) => {
const title = req.params.tag;
const images = await getImagesforTag(config, title);
res.json({ title, images });
};
5 changes: 5 additions & 0 deletions lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import pageHandler from './handlers/page';
import pageDataHandler from './handlers/page-data';
import frontpageDataHandler from './handlers/frontpage-data';
import albumDataHandler from './handlers/album-data';
import tagDataHandler from './handlers/tag-data';
import imageDataHandler from './handlers/image-data';
import { Server } from './definitions/global';

Expand All @@ -31,6 +32,8 @@ export default async (): Promise<Server> => {
commonHandler(app, '/image')
);

server.get('/tag/:tag/', commonHandler(app, '/tag'));

server.get('/:page/', pageHandler(config, app, '/default'));

server.get('/data/index.json', frontpageDataHandler(config));
Expand All @@ -45,6 +48,8 @@ export default async (): Promise<Server> => {
imageDataHandler(config)
);

server.get('/data/tag/(:tag).json', tagDataHandler(config));

server.get('/data/(:page).json', pageDataHandler(config));

// tslint:disable-next-line:no-unnecessary-callback-wrapper
Expand Down
3 changes: 3 additions & 0 deletions lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export const pReadFile = promisify(readFile);

export const stripSlashes = (s: string): string => s.replace(/^\/*|\/*$/g, '');

export const sortAlphabetically = (a: string, b: string): number =>
a.localeCompare(b);

export const log = (...args: any[]) => {
const first = [`馃 ${args.shift()}`];
// tslint:disable-next-line:no-console
Expand Down

0 comments on commit 3ade421

Please sign in to comment.