jsreport-core how add localization? - jsreport

Consider the following example: https://playground.jsreport.net/w/anon/VkLWfMyMb-7
I was able to recreate this using jsreports-online. How can I add localization to jsreport-core?
app.get('/test', (req, res) => {
jsreport().init().then((reporter: Reporter) => {
const templatePath: string = path.join(__dirname, 'assets', 'template.html');
const template: string = fs.readFileSync(templatePath, 'utf8');
const exampledatapath: string = path.join(__dirname, 'assets', 'exampledata.json');
const exampledata: string = fs.readFileSync(exampledatapath, 'utf8');
let data = JSON.parse(exampledata);
reporter.render({
template: {
content: template,
engine: 'handlebars',
recipe: 'chrome-pdf',
},
data: data
}).then(function (out: any) {
out.stream.pipe(res);
})
.catch(function (e: any) {
res.end(e.message);
});
}).catch(function (e: any) {
res.end(e.message);
});
});
Best I could come up with is something like this data['$localizedResource'] = {key1: 'value1'};
Is there a better or build-in way to do it?

You better handle localization on your own in such a case.
Using the resources extension with jsreport-core would require to install additional extension data. Insert resources to the documents store, reference it in the template call...
I see you are anyway sending the template content manually, not using the store. In this case, you better to store your localized files inside plain json, read them using nodejs and extend the input data with it.
reporter.render({
template: {
content: template,
engine: 'handlebars',
recipe: 'chrome-pdf',
},
data: {
...data,
$localizedResource: JSON.parse(fs.readFileSync('....mylabels-en.json').toString())
}
}

Related

NestJS Testing ConfigService works

I am writing an application to handle requests and return predefined responses to allow testing of external REST endpoints by software that cannot have internal tests written into it currently. Therefore, my code uses the Nest JS framework to handle the routes, and then extracts values and returns data. The data returned is stored in external files.
To handle constant changes and different team usage, the program uses a .env file to give the base (root) directory where the files to respond are located. I am trying to write a test case to ensure that the NestJS ConfigService is working properly, but also to use as a base for all my other tests.
With different routes, different data files need to be returned. My code will need to mock all these files. As this data relies on the base ConfigService having read the .env to find the base paths, my routes are based on this starting point.
During development, I have a local .env file with these values set. However, I want to test without this .env file being used, so my tests do not rely on the presence of the .env file, since the CI/CD server, build server, etc., will not have a .env file.
I am simply trying a test file then to work with the configuration, to get set data from my mock.
nestjs-config.service.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { ConfigModule, ConfigService } from '#nestjs/config';
describe('NestJS Configuration .env', () => {
let service: ConfigService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
expandVariables: true,
}),
],
providers: [
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
if (key === 'FILES') {
return './fakedata/';
} else if (key === 'PORT') {
return '9999';
}
return null;
}),
},
},
],
}).compile();
service = module.get<ConfigService>(ConfigService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it.each([
['FILES=', 'FILES', './', './fakedata/'],
['PORT=', 'PORT', '2000', '9999'],
['default value when key is not found', 'NOTFOUND', './', './'],
])('should get from the .env file, %s', (Text: string, Key: string, Default: string, Expected: string) => {
const Result: string = service.get<string>(Key, Default);
expect(Key).toBeDefined();
expect(Result).toBe(Expected);
});
});
The problem in this test is the default values are always returned, meaning the .env file was not read, but the provider had the code to handle this.
Ideally, I would like to create a fake class for testing so I could use it in all my test files. However, when trying to create the fake class, I get an error about other methods missing, that are unrelated to this class.
export class ConfigServiceFake {
get(key: string) {
switch (key) {
case 'FILES':
return './fakedata/';
case 'PORT':
return '9999';
}
}
}
This does not seem to execute, and it appears to still go through the original service.
I was able to adjust this and not need external references, including importing the configuration module, making the mock simpler, and not needing the full definitions.
import { Test, TestingModule } from '#nestjs/testing';
import { ConfigService } from '#nestjs/config';
describe('NestJS Configuration .env', () => {
let service: ConfigService;
afterEach(() => {
jest.clearAllMocks();
});
beforeEach(async () => {
const FakeConfigService = {
provide: ConfigService,
useValue: {
get: jest.fn((Key: string, DefaultValue: string) => {
switch (Key) {
case 'FILES':
return './fakedata/';
break;
case 'PORT':
return '9999';
break;
default:
return DefaultValue;
}
}),
},
};
const module: TestingModule = await Test.createTestingModule({
providers: [FakeConfigService],
}).compile();
service = module.get<ConfigService>(ConfigService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it.each([
['FILES=', 'FILES', './', './fakedata/'],
['PORT=', 'PORT', '2000', '9999'],
['default value when key is not found', 'NOTFOUND', './', './'],
])('should get from the .env file, %s', (Text: string, Key: string, Default: string, Expected: string) => {
const Result: string = service.get<string>(Key, Default);
expect(Key).toBeDefined();
expect(Result).toBe(Expected);
});
});

How do I use #vue/test-utils to setProps and have them show up in the HTML?

I have the my code working in a sandbox and now I am trying to write the test. However, When I try this...
test("Hello World", async () => {
let list = [
{
name: "foo"
}
];
var data = {
list
};
const wrapper = mount(MyComponent, data);
await wrapper.vm.$nextTick();
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).not.toContain("bar");
list.push({
name: "bar"
});
await wrapper.setProps({ list });
await wrapper.vm.$nextTick();
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).toContain("bar");
});
However, expect(wrapper.html()).toContain("bar"); fails because it cannot fine the text. I can see it work using setTimeout so I am not sure what I am missing.
How do I see the prop changes in the html?
Your component is not expecting any props. When you mounting your component you are setting component's data property. And if you want to change it later in test after mounting you should call setData.
Also there is a mistake in your test: according to docs data must be a function.
With all being said your test should look like that:
test("Hello World", async () => {
const list = [
{
name: "foo"
}
];
const data = () => {
list
};
const wrapper = mount(MyComponent, {
data
});
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).not.toContain("bar");
list.push({
name: "bar"
});
await wrapper.setData({ list });
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).toContain("bar");
});

Nestjs - file upload with fastify multipart

I am trying to upload multiple files with nestjs using the fastify adapter. I can do so following the tutorial in this link -article on upload
Now this does the job of file upload using fastify-multipart, but I couldnt make use of the request validations before uploading,
for example, here is my rule-file-models (which later I wanted to save to postgre)
import {IsUUID, Length, IsEnum, IsString, Matches, IsOptional} from "class-validator";
import { FileExtEnum } from "./enums/file-ext.enum";
import { Updatable } from "./updatable.model";
import {Expose, Type} from "class-transformer";
export class RuleFile {
#Expose()
#IsUUID("4", { always: true })
id: string;
#Expose()
#Length(2, 50, {
always: true,
each: true,
context: {
errorCode: "REQ-000",
message: `Filename shouldbe within 2 and can reach a max of 50 characters`,
},
})
fileNames: string[];
#Expose()
#IsEnum(FileExtEnum, { always: true, each: true })
fileExts: string[];
#IsOptional({each: true, message: 'File is corrupated'})
#Type(() => Buffer)
file: Buffer;
}
export class RuleFileDetail extends RuleFile implements Updatable {
#IsString()
#Matches(/[aA]{1}[\w]{6}/)
recUpdUser: string;
}
And I wanted to validate the multipart request and see if these are set properly.
I cannot make it to work with event subscription based approach. Here are a few things I tried - adding the interceptor, to check for the request
#Injectable()
export class FileUploadValidationInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req: FastifyRequest = context.switchToHttp().getRequest();
console.log('inside interceptor', req.body);
// content type cmes with multipart/form-data;boundary----. we dont need to valdidate the boundary
// TODO: handle split errors based on semicolon
const contentType = req.headers['content-type'].split(APP_CONSTANTS.CHAR.SEMI_COLON)[0];
console.log(APP_CONSTANTS.REGEX.MULTIPART_CONTENT_TYPE.test(contentType));
const isHeaderMultipart = contentType != null?
this.headerValidation(contentType): this.throwError(contentType);
**// CANNOT check fir req.file() inside this, as it throws undefined**
return next.handle();
}
headerValidation(contentType) {
return APP_CONSTANTS.REGEX.MULTIPART_CONTENT_TYPE.test(contentType) ? true : this.throwError(contentType);
}
throwError(contentType: string) {
throw AppConfigService.getCustomError('FID-HEADERS', `Request header does not contain multipart type:
Provided incorrect type - ${contentType}`);
}
}
I wasnt able to check req.file() in the above interceptor. It throws as undefined. I tried to follow the fastify-multipart
But I wasnt able to get the request data in a prehandler as provided in the documentation for fastify-multipart
fastify.post('/', async function (req, reply) {
// process a single file
// also, consider that if you allow to upload multiple files
// you must consume all files othwise the promise will never fulfill
const data = await req.file()
data.file // stream
data.fields // other parsed parts
data.fieldname
data.filename
data.encoding
data.mimetype
// to accumulate the file in memory! Be careful!
//
// await data.toBuffer() // Buffer
//
// or
await pump(data.file, fs.createWriteStream(data.filename))
I tried getting via by registering a prehandler hook of my own like this (executed as iife)
(async function bootstrap() {
const appConfig = AppConfigService.getAppCommonConfig();
const fastifyInstance = SERVERADAPTERINSTANCE.configureFastifyServer();
// #ts-ignore
const fastifyAdapter = new FastifyAdapter(fastifyInstance);
app = await NestFactory.create<NestFastifyApplication>(
AppModule,
fastifyAdapter
).catch((err) => {
console.log("err in creating adapter", err);
process.exit(1);
});
.....
app.useGlobalPipes(
new ValidationPipe({
errorHttpStatusCode: 500,
transform: true,
validationError: {
target: true,
value: true,
},
exceptionFactory: (errors: ValidationError[]) => {
// send it to the global exception filter\
AppConfigService.validationExceptionFactory(errors);
},
}),
);
app.register(require('fastify-multipart'), {
limits: {
fieldNameSize: 100, // Max field name size in bytes
fieldSize: 1000000, // Max field value size in bytes
fields: 10, // Max number of non-file fields
fileSize: 100000000000, // For multipart forms, the max file size
files: 3, // Max number of file fields
headerPairs: 2000, // Max number of header key=>value pairs
},
});
(app.getHttpAdapter().getInstance() as FastifyInstance).addHook('onRoute', (routeOptions) => {
console.log('all urls:', routeOptions.url);
if(routeOptions.url.includes('upload')) {
// The registration actually works, but I cant use the req.file() in the prehandler
console.log('###########################');
app.getHttpAdapter().getInstance().addHook('preHandler', FilePrehandlerService.fileHandler);
}
});
SERVERADAPTERINSTANCE.configureSecurity(app);
//Connect to database
await SERVERADAPTERINSTANCE.configureDbConn(app);
app.useStaticAssets({
root: join(__dirname, "..", "public"),
prefix: "/public/",
});
app.setViewEngine({
engine: {
handlebars: require("handlebars"),
},
templates: join(__dirname, "..", "views"),
});
await app.listen(appConfig.port, appConfig.host, () => {
console.log(`Server listening on port - ${appConfig.port}`);
});
})();
Here is the prehandler,
export class FilePrehandlerService {
constructor() {}
static fileHandler = async (req, reply) => {
console.log('coming inside prehandler');
console.log('req is a multipart req',await req.file);
const data = await req.file();
console.log('data received -filename:', data.filename);
console.log('data received- fieldname:', data.fieldname);
console.log('data received- fields:', data.fields);
return;
};
}
This pattern of registring and gettin the file using preHandler works in bare fastify application. I tried it
Bare fastify server:
export class FileController {
constructor() {}
async testHandler(req: FastifyRequest, reply: FastifyReply) {
reply.send('test reading dne');
}
async fileReadHandler(req, reply: FastifyReply) {
const data = await req.file();
console.log('field val:', data.fields);
console.log('field filename:', data.filename);
console.log('field fieldname:', data.fieldname);
reply.send('done');
}
}
export const FILE_CONTROLLER_INSTANCE = new FileController();
This is my route file
const testRoute: RouteOptions<Server, IncomingMessage, ServerResponse, RouteGenericInterface, unknown> = {
method: 'GET',
url: '/test',
handler: TESTCONTROLLER_INSTANCE.testMethodRouteHandler,
};
const fileRoute: RouteOptions = {
method: 'GET',
url: '/fileTest',
preHandler: fileInterceptor,
handler: FILE_CONTROLLER_INSTANCE.testHandler,
};
const fileUploadRoute: RouteOptions = {
method: 'POST',
url: '/fileUpload',
preHandler: fileInterceptor,
handler: FILE_CONTROLLER_INSTANCE.fileReadHandler,
};
const apiRoutes = [testRoute, fileRoute, fileUploadRoute];
export default apiRoutes;
Could someone let me know the right the way to get the fieldnames , validate them befr the service being called in Nestjs
Well, I have done something like this and It works great for me. Maybe it can work for you too.
// main.ts
import multipart from "fastify-multipart";
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
app.register(multipart);
// upload.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
BadRequestException,
} from "#nestjs/common";
import { FastifyRequest } from "fastify";
#Injectable()
export class UploadGuard implements CanActivate {
public async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest() as FastifyRequest;
const isMultipart = req.isMultipart();
if (!isMultipart)
throw new BadRequestException("multipart/form-data expected.");
const file = await req.file();
if (!file) throw new BadRequestException("file expected");
req.incomingFile = file;
return true;
}
}
// file.decorator.ts
import { createParamDecorator, ExecutionContext } from "#nestjs/common";
import { FastifyRequest } from "fastify";
export const File = createParamDecorator(
(_data: unknown, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest() as FastifyRequest;
const file = req.incomingFile;
return file
},
);
// post controller
#Post("upload")
#UseGuards(UploadGuard)
uploadFile(#File() file: Storage.MultipartFile) {
console.log(file); // logs MultipartFile from "fastify-multipart"
return "File uploaded"
}
and finally my typing file
declare global {
namespace Storage {
interface MultipartFile {
toBuffer: () => Promise<Buffer>;
file: NodeJS.ReadableStream;
filepath: string;
fieldname: string;
filename: string;
encoding: string;
mimetype: string;
fields: import("fastify-multipart").MultipartFields;
}
}
}
declare module "fastify" {
interface FastifyRequest {
incomingFile: Storage.MultipartFile;
}
}
So I found a simpler alternative. I started using fastify-multer. I used it along with this awesome lib - which made me use the multer for fastify - #webundsoehne/nest-fastify-file-upload
These are the changes I made. I registered the multer content process.
app.register(multer( {dest:path.join(process.cwd()+'/upload'),
limits:{
fields: 5, //Number of non-file fields allowed
files: 1,
fileSize: 2097152,// 2 MB,
}}).contentParser);
Then in the controller - I use it as the nestjs doc says . This actually makes fasitfy work with multer
#UseInterceptors(FileUploadValidationInterceptor, FileInterceptor('file'))
#Post('/multerSample')
async multerUploadFiles(#UploadedFile() file, #Body() ruleFileCreate: RuleFileCreate) {
console.log('data sent', ruleFileCreate);
console.log(file);
// getting the original name of the file - no matter what
ruleFileCreate.originalName = file.originalname;
return await this.fileService.fileUpload(file.buffer, ruleFileCreate);
}
BONUS - storing the file in local and storing it in DB - Please refer
github link

How to create types.array in MST from external api - always returning proxy not object

I'm fetching data from external API, and want to store it in MST store as array. But the result is always proxy, not the object I wanted.
This is result from API:
(4) [Object, Object, Object, Object]
0:Object
id:1
name: "Foobar"
created_at: "2019-04-27 09:09:29"
updated_at:null
deleted_at:null
__proto__:Object
.........
This is my store:
const TypesModel = types.model({
name: types.maybe(types.string),
created_at: types.maybe(types.string)
});
export const TransactionTypeStore = types
.model("TransactionTypeStore", {
transaction_types: types.optional(types.array(TypesModel), [])
})
.actions(self => ({
getTypes: flow(function*(token) {
try {
const res = yield typesApi
.headers({ Authorization: `Bearer ${token}` })
.get()
.json();
console.log("result", res);
self.transaction_types = res;
// res.map(data => {
// self.transaction_types.push(data);
// });
} catch (err) {
console.log(err);
}
})
}));
And this is console.log of my MST store:
transaction_types:Proxy
[[Handler]]:Object
[[Target]]:Array(4)
0:ObjectNode
1:ObjectNode
2:ObjectNode
3:ObjectNode
$treenode:ObjectNode
length:4
toJSON:function toJSON()
Symbol(mobx administration):ObservableArrayAdministration
__proto__:Array(0)
[[IsRevoked]]:false
.........
Does anyone know how to deal with this kind of problem?
its similar to TypeScript, only you are using Mobx .MODEL
So lets say we want to create a ToDo list, which array that has id: number, name: string, isDone: boolean.
You first Define this Interface using Mobx .model, like this:
const singleToDoItem = types.model({
id: types.number,
name: types.string,
isDone: types.boolean
})
we then create an Actual Store, with ARRAY as a type (you can also use .optional) AND then put the singleToDoItem inside the types.array(singleToDoItem), so it looks like this:
const myStore = types.model({
list: types.array(singleToDoItem)
})
FINAL CODE will look like this:
const singleToDoItem = types.model({
id: types.number,
name: types.string,
isDone: types.boolean })
const myStore = types.model({
toDoList: types.array(singleToDoItem) })
I made a video on how to do this on my YouTube channel.
How does your res object look like, it should be an array of { name, created_at } objects - nothing more nothing less, is it? Also transaction_types will never be a mere array - types.array is a complex MST type, it has some array methods, but it's not an array. It's an observable array and you should treat it accordingly.
Also check this video tutorial by Michel Weststrate himself: Use observable objects, arrays, and maps to store state in MobX to get a better grip on the concept (create an account, it's free).

Nuxt serverMiddleware get json from API

Instead of getting redirects from 301.json I want to make a request to my api which returns my json.
I am using the #nuxtjs/axios module.
const redirects = require('../301.json');
export default function (req, res, next) {
const redirect = redirects.find(r => r.from === req.url);
if (redirect) {
console.log('redirect: ${redirect.from} => ${redirect.to}');
res.writeHead(301, { Location: redirect.to });
res.end();
} else {
next();
}
}
Original answer
To build on #Dominooch's answer, if you want to return just JSON, you can use the .json() helper. It automatically sets the content-type to application/json and stringify's an object you pass it.
edit:
To clarify what we're doing here, we're replacing your 301.json entirely and using nuxt's way of creating middleware to:
define a generic handler that you can reuse for any route
defining explicitly which paths will use your handler (what I'm assuming you're 301.json is doing)
If 301.json is really just an array of paths that you want to redirect, then you can just use .map() but i'd personally not, because it's not immediately clear which paths are getting redirected (see my last sample)
That said, the very last thing I would avoid is making a global middleware (fires for every request) that checks to see if the path is included in your array. <- Will make route handling longer for each item in the array. Using .map() will make nuxt do the route matching for you (which it already does anyways) instead of sending every request through your handler.
// some-api-endpoint.js
import axios from 'axios'
export default {
path: '/endpoint'
handler: async (req, res) => {
const { data } = await axios.get('some-request')
res.json(data)
}
}
Then in your nuxt.config.js:
// nuxt.config.js
module.exports = {
// some other exported properties ...
serverMiddleware: [
{ path: '/endpoint', handler: '~/path/to/some-api-endpoint.js' },
]
}
If 301.json is really just an array of paths:
// nuxt.config.js
const routes = require('../301.json');
module.exports = {
// some other exported properties ...
serverMiddleware: routes.map(path =>
({ path, handler: '~/path/to/some-api-endpoint.js' }))
}
Or if you have other middleware:
// nuxt.config.js
const routes = require('../301.json');
module.exports = {
// some other exported properties ...
serverMiddleware: [
...routes.map(path =>
({ path, handler: '~/path/to/some-api-endpoint.js' })),
... // Other middlewares
}
Here's what I did and it seems to work:
//uri-path.js
import axios from 'axios'
export default {
path: '/uri/path',
async handler (req, res) {
const { data } = await axios.get('http://127.0.0.1:8000/uri/path')
res.setHeader('Content-Type', 'text/html')
res.end(data)
}
}