mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-02 21:32:42 +00:00
Consolidate and add theme parent walking
This consolidates the style/theme endpoint to a singular method, adds interpretation of static themes, and allow parent theme recursion. I am not 100% sure this will order styles correctly.
This commit is contained in:
@@ -11,6 +11,19 @@ const { nanoid } = require('nanoid');
|
|||||||
|
|
||||||
const { DEFAULT_BREW, DEFAULT_BREW_LOAD } = require('./brewDefaults.js');
|
const { DEFAULT_BREW, DEFAULT_BREW_LOAD } = require('./brewDefaults.js');
|
||||||
|
|
||||||
|
const themes = require('../themes/themes.json');
|
||||||
|
|
||||||
|
const isStaticTheme = (engine, themeName)=>{
|
||||||
|
if(!themes.hasOwnProperty(engine)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if(themes[engine].hasOwnProperty(themeName)) {
|
||||||
|
return themes[engine][themeName].baseTheme;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// const getTopBrews = (cb) => {
|
// const getTopBrews = (cb) => {
|
||||||
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
|
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
|
||||||
// cb(brews);
|
// cb(brews);
|
||||||
@@ -19,6 +32,22 @@ const { DEFAULT_BREW, DEFAULT_BREW_LOAD } = require('./brewDefaults.js');
|
|||||||
|
|
||||||
const MAX_TITLE_LENGTH = 100;
|
const MAX_TITLE_LENGTH = 100;
|
||||||
|
|
||||||
|
const splitTextStyleAndMetadata = (brew)=>{
|
||||||
|
brew.text = brew.text.replaceAll('\r\n', '\n');
|
||||||
|
if(brew.text.startsWith('```metadata')) {
|
||||||
|
const index = brew.text.indexOf('```\n\n');
|
||||||
|
const metadataSection = brew.text.slice(12, index - 1);
|
||||||
|
const metadata = yaml.load(metadataSection);
|
||||||
|
Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang']));
|
||||||
|
brew.text = brew.text.slice(index + 5);
|
||||||
|
}
|
||||||
|
if(brew.text.startsWith('```css')) {
|
||||||
|
const index = brew.text.indexOf('```\n\n');
|
||||||
|
brew.style = brew.text.slice(7, index - 1);
|
||||||
|
brew.text = brew.text.slice(index + 5);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
homebrewApi : router,
|
homebrewApi : router,
|
||||||
getId : (req)=>{
|
getId : (req)=>{
|
||||||
@@ -40,6 +69,7 @@ const api = {
|
|||||||
getBrew : (accessType, stubOnly = false)=>{
|
getBrew : (accessType, stubOnly = false)=>{
|
||||||
// Create middleware with the accessType passed in as part of the scope
|
// Create middleware with the accessType passed in as part of the scope
|
||||||
return async (req, res, next)=>{
|
return async (req, res, next)=>{
|
||||||
|
|
||||||
// Get relevant IDs for the brew
|
// Get relevant IDs for the brew
|
||||||
const { id, googleId } = api.getId(req);
|
const { id, googleId } = api.getId(req);
|
||||||
|
|
||||||
@@ -205,65 +235,29 @@ const api = {
|
|||||||
|
|
||||||
res.status(200).send(saved);
|
res.status(200).send(saved);
|
||||||
},
|
},
|
||||||
getBrewTheme : async (req, res)=>{
|
|
||||||
req.brew.text = req.brew.text.replaceAll('\r\n', '\n');
|
|
||||||
if(req.brew.text.startsWith('```metadata')) {
|
|
||||||
const index = req.brew.text.indexOf('```\n\n');
|
|
||||||
const metadataSection = req.brew.text.slice(12, index - 1);
|
|
||||||
const metadata = yaml.load(metadataSection);
|
|
||||||
Object.assign(req.brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang']));
|
|
||||||
req.brew.text = req.brew.text.slice(index + 5);
|
|
||||||
}
|
|
||||||
if(req.brew.text.startsWith('```css')) {
|
|
||||||
const index = req.brew.text.indexOf('```\n\n');
|
|
||||||
req.brew.style = req.brew.text.slice(7, index - 1);
|
|
||||||
req.brew.text = req.brew.text.slice(index + 5);
|
|
||||||
}
|
|
||||||
return res.status(200).send({
|
|
||||||
parent : req.brew.theme,
|
|
||||||
theme : req.brew.style,
|
|
||||||
themeName : req.brew.title
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getBrewThemeAsCSS : async (req, res)=>{
|
|
||||||
req.brew.text = req.brew.text.replaceAll('\r\n', '\n');
|
|
||||||
if(req.brew.text.startsWith('```metadata')) {
|
|
||||||
const index = req.brew.text.indexOf('```\n\n');
|
|
||||||
const metadataSection = req.brew.text.slice(12, index - 1);
|
|
||||||
const metadata = yaml.load(metadataSection);
|
|
||||||
Object.assign(req.brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang']));
|
|
||||||
req.brew.text = req.brew.text.slice(index + 5);
|
|
||||||
}
|
|
||||||
if(req.brew.text.startsWith('```css')) {
|
|
||||||
const index = req.brew.text.indexOf('```\n\n');
|
|
||||||
req.brew.style = req.brew.text.slice(7, index - 1);
|
|
||||||
req.brew.text = req.brew.text.slice(index + 5);
|
|
||||||
}
|
|
||||||
if(res.hasOwnProperty('set')) {
|
|
||||||
res.set('Content-Type', 'text/css');
|
|
||||||
}
|
|
||||||
return res.status(200).send(`// From Theme: ${req.brew.title}\n\n${req.brew.style}`);
|
|
||||||
},
|
|
||||||
getBrewThemeWithCSS : async (req, res)=>{
|
getBrewThemeWithCSS : async (req, res)=>{
|
||||||
req.brew.text = req.brew.text.replaceAll('\r\n', '\n');
|
const brew = req.brew;
|
||||||
if(req.brew.text.startsWith('```metadata')) {
|
splitTextStyleAndMetadata(brew);
|
||||||
const index = req.brew.text.indexOf('```\n\n');
|
|
||||||
const metadataSection = req.brew.text.slice(12, index - 1);
|
|
||||||
const metadata = yaml.load(metadataSection);
|
|
||||||
Object.assign(req.brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang']));
|
|
||||||
req.brew.text = req.brew.text.slice(index + 5);
|
|
||||||
}
|
|
||||||
if(req.brew.text.startsWith('```css')) {
|
|
||||||
const index = req.brew.text.indexOf('```\n\n');
|
|
||||||
req.brew.style = req.brew.text.slice(7, index - 1);
|
|
||||||
req.brew.text = req.brew.text.slice(index + 5);
|
|
||||||
}
|
|
||||||
if(res.hasOwnProperty('set')) {
|
if(res.hasOwnProperty('set')) {
|
||||||
res.set('Content-Type', 'text/css');
|
res.set('Content-Type', 'text/css');
|
||||||
}
|
}
|
||||||
const parentThemeImport = `// From Theme: ${req.brew.title}\n\n@import /themes/${req.brew.renderer}/${req.brew.theme}/styles.css\n\n`;
|
const staticTheme = `/api/css/${req.brew.renderer}/${req.brew.theme}/styles.css`;
|
||||||
|
const userTheme = `/api/css/${req.brew.theme.slice(1)}`;
|
||||||
|
const parentThemeImport = `// From Theme: ${req.brew.title}\n\n@import ${req.brew.theme[0] != '#' ? staticTheme : userTheme}\n\n`;
|
||||||
return res.status(200).send(`${req.brew.renderer == 'legacy' ? '' : parentThemeImport}${req.brew.style}`);
|
return res.status(200).send(`${req.brew.renderer == 'legacy' ? '' : parentThemeImport}${req.brew.style}`);
|
||||||
},
|
},
|
||||||
|
getStaticTheme : async(req, res)=>{
|
||||||
|
const themeParent = isStaticTheme(req.params.engine, req.params.id);
|
||||||
|
if(themeParent === undefined){
|
||||||
|
res.status(404).send(`Invalid Theme - Engine: ${req.params.engine}, Name: ${req.params.id}`);
|
||||||
|
} else {
|
||||||
|
if(res.hasOwnProperty('set')) {
|
||||||
|
res.set('Content-Type', 'text/css');
|
||||||
|
}
|
||||||
|
const parentTheme = themeParent ? `@import /api/css/${req.params.engine}/${themeParent}\n` : '';
|
||||||
|
return res.status(200).send(`${parentTheme}@import /themes/${req.params.engine}/${req.params.id}\n`);
|
||||||
|
}
|
||||||
|
},
|
||||||
updateBrew : async (req, res)=>{
|
updateBrew : async (req, res)=>{
|
||||||
// Initialize brew from request and body, destructure query params, and set the initial value for the after-save method
|
// Initialize brew from request and body, destructure query params, and set the initial value for the after-save method
|
||||||
const brewFromClient = api.excludePropsFromUpdate(req.body);
|
const brewFromClient = api.excludePropsFromUpdate(req.body);
|
||||||
@@ -424,9 +418,7 @@ router.put('/api/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api
|
|||||||
router.put('/api/update/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
|
router.put('/api/update/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
|
||||||
router.delete('/api/:id', asyncHandler(api.deleteBrew));
|
router.delete('/api/:id', asyncHandler(api.deleteBrew));
|
||||||
router.get('/api/remove/:id', asyncHandler(api.deleteBrew));
|
router.get('/api/remove/:id', asyncHandler(api.deleteBrew));
|
||||||
router.get('/api/theme/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.getBrewTheme));
|
router.get('/api/css/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.getBrewThemeWithCSS));
|
||||||
router.get('/api/css/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.getBrewThemeAsCSS));
|
router.get('/api/css/:engine/:id/', asyncHandler(api.getStaticTheme));
|
||||||
router.get('/api/csstheme/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.getBrewThemeWithCSS));
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = api;
|
module.exports = api;
|
||||||
|
|||||||
@@ -569,64 +569,8 @@ brew`);
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getBrewTheme', ()=>{
|
describe('getBrewThemeWithStaticParent', ()=>{
|
||||||
it('should collect parent theme and brew style', async ()=>{
|
it('should collect parent theme and brew style - returning as css with static parent imported.', async ()=>{
|
||||||
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
|
|
||||||
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', style: 'I Have a style!' }));
|
|
||||||
const brewResults = {
|
|
||||||
authors : [],
|
|
||||||
createdAt : undefined,
|
|
||||||
description : '',
|
|
||||||
editId : undefined,
|
|
||||||
gDrive : false,
|
|
||||||
lang : 'en',
|
|
||||||
pageCount : 1,
|
|
||||||
published : false,
|
|
||||||
renderer : 'legacy',
|
|
||||||
shareId : undefined,
|
|
||||||
style : 'I Have a style!',
|
|
||||||
systems : [],
|
|
||||||
tags : [],
|
|
||||||
text : '',
|
|
||||||
theme : '5ePHB',
|
|
||||||
thumbnail : '',
|
|
||||||
title : 'test brew',
|
|
||||||
trashed : false,
|
|
||||||
updatedAt : undefined,
|
|
||||||
views : 0
|
|
||||||
};
|
|
||||||
const fn = api.getBrew('share', true);
|
|
||||||
const req = { brew: {} };
|
|
||||||
const next = jest.fn();
|
|
||||||
await fn(req, null, next);
|
|
||||||
|
|
||||||
api.getBrewTheme(req, res);
|
|
||||||
const sent = res.send.mock.calls[0][0];
|
|
||||||
expect(req.brew).toStrictEqual(brewResults);
|
|
||||||
expect(res.status).toHaveBeenCalledWith(200);
|
|
||||||
expect(sent.parent).toBe('5ePHB');
|
|
||||||
expect(sent.theme).toBe('I Have a style!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getBrewAsThemeCSS', ()=>{
|
|
||||||
it('should collect the brew style - returning as css', async ()=>{
|
|
||||||
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
|
|
||||||
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', style: 'I Have a style!' }));
|
|
||||||
const fn = api.getBrew('share', true);
|
|
||||||
const req = { brew: {} };
|
|
||||||
const next = jest.fn();
|
|
||||||
await fn(req, null, next);
|
|
||||||
|
|
||||||
api.getBrewThemeAsCSS(req, res);
|
|
||||||
const sent = res.send.mock.calls[0][0];
|
|
||||||
expect(sent).toBe('I Have a style!');
|
|
||||||
expect(res.status).toHaveBeenCalledWith(200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getBrewThemeWithCSS', ()=>{
|
|
||||||
it('should collect parent theme and brew style - returning as css with parent imported.', async ()=>{
|
|
||||||
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
|
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
|
||||||
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', renderer: 'V3', style: 'I Have a style!' }));
|
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', renderer: 'V3', style: 'I Have a style!' }));
|
||||||
const fn = api.getBrew('share', true);
|
const fn = api.getBrew('share', true);
|
||||||
@@ -636,11 +580,67 @@ brew`);
|
|||||||
|
|
||||||
api.getBrewThemeWithCSS(req, res);
|
api.getBrewThemeWithCSS(req, res);
|
||||||
const sent = res.send.mock.calls[0][0];
|
const sent = res.send.mock.calls[0][0];
|
||||||
expect(sent).toBe(`@import /themes/V3/5ePHB/styles.css\n\nI Have a style!`);
|
expect(sent).toBe(`// From Theme: test brew\n\n@import /api/css/V3/5ePHB/styles.css\n\nI Have a style!`);
|
||||||
expect(res.status).toHaveBeenCalledWith(200);
|
expect(res.status).toHaveBeenCalledWith(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getBrewThemeWithUserParent', ()=>{
|
||||||
|
it('should collect parent theme and brew style - returning as css with user-theme parent imported.', async ()=>{
|
||||||
|
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
|
||||||
|
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', renderer: 'V3', theme: '#IamATheme', style: 'I Have a style!' }));
|
||||||
|
const fn = api.getBrew('share', true);
|
||||||
|
const req = { brew: {} };
|
||||||
|
const next = jest.fn();
|
||||||
|
await fn(req, null, next);
|
||||||
|
|
||||||
|
api.getBrewThemeWithCSS(req, res);
|
||||||
|
const sent = res.send.mock.calls[0][0];
|
||||||
|
expect(sent).toBe(`// From Theme: test brew\n\n@import /api/css/IamATheme\n\nI Have a style!`);
|
||||||
|
expect(res.status).toHaveBeenCalledWith(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getStaticTheme', ()=>{
|
||||||
|
it('should return an import of the theme without including a parent.', async ()=>{
|
||||||
|
const req = {
|
||||||
|
params : {
|
||||||
|
engine : 'V3',
|
||||||
|
id : '5ePHB'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
api.getStaticTheme(req, res);
|
||||||
|
const sent = res.send.mock.calls[0][0];
|
||||||
|
expect(sent).toBe('@import /themes/V3/5ePHB\n');
|
||||||
|
expect(res.status).toHaveBeenCalledWith(200);
|
||||||
|
});
|
||||||
|
it('should return an import of the theme including a parent.', async ()=>{
|
||||||
|
const req = {
|
||||||
|
params : {
|
||||||
|
engine : 'V3',
|
||||||
|
id : '5eDMG'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
api.getStaticTheme(req, res);
|
||||||
|
const sent = res.send.mock.calls[0][0];
|
||||||
|
expect(sent).toBe('@import /api/css/V3/5ePHB\n@import /themes/V3/5eDMG\n');
|
||||||
|
expect(res.status).toHaveBeenCalledWith(200);
|
||||||
|
});
|
||||||
|
it('should fail for an invalid static theme.', async()=>{
|
||||||
|
const req = {
|
||||||
|
params : {
|
||||||
|
engine : 'V3',
|
||||||
|
id : '5eDMGGGG'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
api.getStaticTheme(req, res);
|
||||||
|
const sent = res.send.mock.calls[0][0];
|
||||||
|
expect(sent).toBe('Invalid Theme - Engine: V3, Name: 5eDMGGGG');
|
||||||
|
expect(res.status).toHaveBeenCalledWith(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('deleteBrew', ()=>{
|
describe('deleteBrew', ()=>{
|
||||||
it('should handle case where fetching the brew returns an error', async ()=>{
|
it('should handle case where fetching the brew returns an error', async ()=>{
|
||||||
api.getBrew = jest.fn(()=>async ()=>{ throw { message: 'err', HBErrorCode: '02' }; });
|
api.getBrew = jest.fn(()=>async ()=>{ throw { message: 'err', HBErrorCode: '02' }; });
|
||||||
|
|||||||
Reference in New Issue
Block a user