I'm building a custom API, with an endpoint defined as follow:
I have a created a Profiling Controller to handle the logic for this endpoint. My controller directory contains 2 files:
controller.ts
import { Request, Response } from 'express';
import ProfilingService from '../../services/profiling.service';
export class Controller {
enrich_profile(req: Request, res: Response): void {
console.log(req);
ProfilingService.enrich_profile(req).then((r) =>
res
.status(201)
.location(`/api/v1/profile_data/enrich_profile/data${r}`)
.json(r)
);
}
}
export default new Controller();
routes.ts
/* eslint-disable prettier/prettier */
import express from 'express';
import controller from './controller';
export default express.
Router()
.post('/enrich_profile', controller.enrich_profile)
;
However, when I sent a call to the endpoint, I get the following error:
And finally, to allow a full picture, here's the content of profiling.service.ts:
import L from '../../common/logger';
interface Profiling {
data: never;
}
export class ProfilingService {
enrich_profile(data: never): Promise<Profiling> {
console.log(data);
L.info(`update user profile using \'${data}\'`);
const profile_data: Profiling = {
data,
};
return Promise.resolve(profile_data);
}
}
export default new ProfilingService();
The error clearly states that POST to the defined endpoint is not possible, and I'm not sure I understand why.
How can I fix this issue?
I'm not sure what was causing the issue, but after cleaning my npm cache, and deleting node_modules about 100X, the issue finally went away.
Related
I am using Sinon with Ember.js Concurrency Tasks and am trying to stub the task in a test.
The code looks something like this:
component .ts file:
import Component from '#glimmer/component';
import { TaskGenerator, TaskInstance } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor } from 'ember-concurrency-ts';
export default class Container extends Component<Args> {
#task *myTask(): TaskGenerator<Data> {
const response: Data = yield json('someURL'); //json() returns a JSON object from a request to someURL
return response;
}
get task(): TaskInstance<Data> | null {
const task = taskFor(this.myTask);
return task.last ? task.last : task.perform();
}
#action
someMethod(): void {
const task = taskFor(this.myTask);
task.perform();
}
}
relevant test from component test file:
...
module('Integration | Component | container', function(hooks){
test('some test', async function(this: Context, assert) {
await render(hbs`
<Container #someMethod={{#someArgument}} as |c| >
// some code that uses c
</Container>
`);
}
How would I stub the myTask task? I would essentially like to have it so that I am able to manually control the response that comes out of myTask so an HTTP response doesn't have to be made in the test.
I would extend the component in your test file with your mocked task overriding the real one.
class TestContainer extends Container {
#task *myTask(): TaskGenerator<Data> {
return someMockData;
}
}
// ...
hooks.beforeEach(function() {
this.owner.register('component:container', TestContainer);
});
I'm not aware of any way to mock a single task in a component for testing. When the network is involved I reach for ember-cli-mirage which is built on pretender. Mirage is very good when working with ember-data models and can also be used to handle mocking any network request. If you're not using ember-data you may want to just use pretender or investigate the non-framework Mirage.js.
By mocking the network and returning canned data you will have the same control over your tests while testing the component as is. I really like this approach and have found it to be very reliable and stable for several years.
I do have tasks stubbing with sinon in my project. It's built a bit differently from the setup you have but perhaps you might get some inspiration.
So I have this task in my component
#(task(function* () {
yield this.exportxls.asXls.perform(someArg);
})) downloadXls;
and this asXls method is in the service
#(task(function* (mapping) {
// ...
}).drop()) asXls;
and then in my integration test I do the stub like this
this.owner.register('service:exportxls', Service.extend({
init() {
this._super(...arguments);
this.set('asXls', {
perform: sinon.stub()
});
}
}));
after that I can do usual checks
assert.ok(exportService.asXls.perform.calledOnce);
I have an application which uses nestjs and MiddlewareConsumer.
I would like to know if there's a way to skip a middleware based on a header value?
I checked documentation and saw that I can only use path or method (as I do now) but maybe there's something I'm missing?
Sample of my code:
export class AuthorizationModule implements NestModule {
configure(consumer: MiddlewareConsumer): void {
consumer.apply(DiscriminatorValidator).with(common.USERS).forRoutes(
{path: RELATIVE_RESOURCE_PATH, method: RequestMethod.POST},{path: RELATIVE_RESOURCE_PATH, method: RequestMethod.PUT});
consumer.apply(validate).forRoutes(AuthorizationController);
consumer.apply(HeadersValidator).with().forRoutes(AuthorizationController);
consumer.apply(ContextAndHeadersMiddleware).forRoutes(AuthorizationController);
}
}
This is not possible with the MiddlewareConsumer.
However, the middleware itself can check if its applicable or should be skipped:
#Injectable()
export class ContextAndHeadersMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
if (req.get('my-header') === 'SKIP') {
// skip this middleware if header value is set
return next();
}
// middleware logic
}
}
UPDATE:
Can anyone help? I have been pursuing this without luck for the better half of this week. I do notice that the client is generating two POSTs. I have added code for the adapter. Is there anywhere else I should be looking?
I am going through the video tutorial provided below and am unable to resolve two errors when I click the submit button to save data to the database.
No model was found for 'user'
Two POSTs are being generated. This results in an Assertion Failed error, which I suspect is because the ID returned from the server does not match the current ID on the front-end.
I see that the database has two new records. When I click on the submit button again then the application takes me back to the todo-items page where it shows the two records. Can anyone advise what I am doing wrong?
Current versions:
Ember : 3.2.2
Ember Data : 3.2.0
jQuery : 3.3.1
Ember Simple Auth : 1.7.0
Video tutorial (the error occurs at the 11:30 mark): https://www.youtube.com/watch?v=bZ1D_aYGJnU. Note: the author of the video seems to have gotten the duplicate POST issue to go away right at the end of the video, but I do not see how.
Component/forms/todo-item-form/component.js
import Component from '#ember/component';
export default Component.extend({
actions:{
save(){
this.get('submit')();
}
}
});
Component/forms/todo-item-form/template.hbs
<form {{action "save" on="submit"}}>
{{input placeholder="description" value=todoItem.description}}
<br />
{{#if todoItem.validations.isValid}}
<button type="submit">Add</button>
{{else}}
<button type="submit" disabled>Add</button>
{{/if}}
</form>
templates/s/todo-items/add.hbs
{{forms/todo-item-form
todoItem=model
submit=(route-action "submitAction")
}}
{{outlet}}
models/todo-item.js
import DS from 'ember-data';
import { validator, buildValidations } from 'ember-cp-validations';
const { attr, belongsTo } = DS;
const Validations = buildValidations({
description: [
validator('presence', true),
validator('length', {
min: 4
})
]
});
export default DS.Model.extend(Validations, {
description: attr('string'),
owner: belongsTo('person')
});
adapter/Application.js
import DS from 'ember-data';
import ENV from 'todo-list-client/config/environment';
const {computed, inject :{service} } = Ember;
export default DS.JSONAPIAdapter.extend({
session: service(),
namespace: ENV.APP.namespace,
host: ENV.APP.host,
headers: computed('session.data.authenticated.token', function() {
let token = this.get('session.data.authenticated.access_token');
return { Authorization: `Bearer ${token}` };
}),
})
routes/s/todo-items/add.js
import Route from '#ember/routing/route';
export default Route.extend({
model(){
return this.store.createRecord('todo-item');
},
actions: {
submitAction() {
this.get('controller.model')
.save()
.then(() => {
this.transitionTo('s.todo-items');
});
}
},
});
The author adds Ember-Data-Route at about 15m5s for the add.js route as a mixin. This cleans up after the model.
He starts the explanation at that point, adds it in over the next minute or two in the video:
https://youtu.be/bZ1D_aYGJnU?t=15m5s
import Ember from 'ember';
import DataRoute from 'ember-data-route';
export default Ember.Route.extend(DataRoute, {
model() {
return this.store.createRecord('todo-item');
},
actions: {
save() {
this.get('controller.model')
.save()
.then(() => {
this.transitionTo('s.todo-items');
});
}
},
});
I'm having trouble getting Ember's queryRecord to work properly. I'm trying to grab a site config from the server.
//app/routes/application.js
model: function(){
return this.get('store').queryRecord('config',{}).then(function(config) {
console.log(config.get('appname'));
});
}
//app/adapters/config.js
import DS from "ember-data";
import ENV from './../config/environment';
export default DS.Adapter.extend({
queryRecord(modelName, query) {
return Ember.$.getJSON( ENV.APP.apiFull + 'config' );
}
});
//app/serializers/applications.js
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
keyForAttribute: function(attr, method) {
return Ember.String.underscore(attr).toUpperCase();
}
});
//JSON returning from server AJAX call
{"config":{"id":1,"environment": "development", "appname":"Sample App Name"}}
The console.log statement in //app/routes/application is returning undefined. This all seems to match up with the Ember documentation for version 2.9. What am I doing incorrectly?
#Lux, thanks for pointing me towards the serializer. That led to me looking at the model member names and I found that I had underscored them in the model, but not in the JSON coming from the server.
I've been successfully calling Meteor methods until I created a new Mongo collection. Both collections are found under /imports/collections/, so I know it's available to both client and server.
Here is the Meteor.method, which is pretty much the same as my working collection.:
import { Mongo } from 'meteor/mongo';
Meteor.methods({
'messages.insert': function(data) {
return Messages.insert({
otherCollectionId: data.otherCollectionId,
type: data.type,
createdAt: new Date(),
sender: this.userId,
text: data.text
});
}
});
export const Messages = new Mongo.Collection('messages');
Here's how I call it:
import React from 'react';
import { Messages } from '../../imports/collections/messages';
// other imports
export default class Chat extends React.Component {
// other code
handleComposeClick() {
if (this.refs.text) {
let data = {
playlistId: this.props.playlist._id,
type: 'user',
text: this.refs.text.value
};
Meteor.call('messages.insert', data, (error, playlistId) => {
if (!error) {
this.setState({error: ''});
this.refs.text.value = '';
} else {
this.setState({ error });
console.log(error);
}
});
}
}
// render()
}
Whenever I click and trigger handleComposeClick(), I get this error:
errorClass {error: 404, reason: "Method 'messages.insert' not found", details: undefined, message: "Method 'messages.insert' not found [404]", errorType: "Meteor.Error"}
Remember that anything inside the /imports folder will not work unless it's actually imported, either with this syntax:
import somethingINeed from '/imports/wherever/stuff';
import { somethingElseINeed } from '/imports/wherever/things';
or:
import '/imports/server/stuff';
So for methods, you may want to set up a structure where you have the following:
/lib/main.js
import '../imports/startup/lib';
/imports/startup/lib/index.js
import './methods';
// any other shared code you need to import
/imports/startup/lib/methods.js
import { Meteor } from 'meteor/meteor';
import { Messages } from '/imports/collections/messages'; // don't define your collections in a methods file
Meteor.methods({
'messages.insert': function(data) {
return Messages.insert({
otherCollectionId: data.otherCollectionId,
type: data.type,
createdAt: new Date(),
sender: this.userId,
text: data.text
});
}
});
Though if I were you, I'd use validated methods, where you actually import the method you want to use in order to use it, e.g.:
import { messagesInsert } from '/imports/common-methods/message-methods';
// code...
messagesInsert.call({ arg1, arg2 }, (error, result) => { /*...*/ });
With this structure, you would instead have /server/main.js import ../imports/startup/server which would import ./methods, which (in my particular project) looks like this:
// All methods have to be imported here, so they exist on the server
import '../../common-methods/attachment-methods';
import '../../common-methods/comment-methods';
import '../../common-methods/tag-methods';
import '../../common-methods/notification-methods';
import '../../features/Admin/methods/index';
import '../../features/Insights/methods';
import '../../features/Messages/methods';
Keep in mind, this doesn't actually execute the methods, it just makes sure they're defined on the server, so that when you import these validated methods on the client side and run them, it doesn't bomb out saying the method can't be found on the server side.
Import 'Messages' in your server side (/server/main.js)
Shout out to #MasterAM for helping me find my typo. Turns out I just had an incorrect path in /server/main.js