-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type ObjectID for model property #1875
Comments
I created a mongoDB DataSource, my model contains the following: @model()
export class Customer extends Entity {
@property({
type: 'string',
id: true,
})
_id?: string;
@property({
type: 'string',
})
fname?: string;
constructor(data?: Partial<Customer>) {
super(data);
} and I could post some records _(without including id property, since it is automatically generated by mongoDB) and was able to retrieve some data: [{"_id":"5bc8e9ac964667e062645686","fname":"mario"},{"_id":"5bc8e9b8964667e062645687","fname":"laura"}] Is this what you meant? , or you meant a direct manipulation of the ObjectID object by the connector? |
@marioestradarosa I want to define the property type as ObjectID: @model()
export class Customer extends Entity {
@property({
type: 'ObjectID',
id: true,
})
_id?: ObjectID;
@property({
type: 'string',
})
fname?: string;
@property({
type: 'ObjectID',
})
userId?: ObjectID;
constructor(data?: Partial<Customer>) {
super(data);
}
} In the mongoDB connector there is
|
@mrbatista for what I understood in the documentation for this connector the flag ObjectID however, can't be set as the property type directly in the model for now.
But in the mongodb, it was converted to object id automatically. Architecturally, by default the _id field is an ObjectID. {
"_id": {
"$oid": "5bc8e9ac964667e062645686"
},
"fname": "mario"
} In your case, are you looking to provide these $oid values automatically to MongoDB? like in your case where you specified _id and userId ? |
So, in this method https://github.com/strongloop/loopback-connector-mongodb/blob/efbee59bfe8362822483d5e1a0867e90c1b00c4d/lib/mongodb.js#L1852 we never reach the return true on line 1858 for now. |
I'm facing a similar problem. @model()
export class MongoEntity extends Entity {
@property({
type: 'string',
id: true
})
id: string;
constructor(data?: Partial<MongoEntity>) {
super(data);
}
} mongo-entity.repository.ts const ObjectId = require('mongodb').ObjectId
export class MongoEntityRepository<E extends MongoEntity, ID>
extends DefaultCrudRepository<E, ID> {
async create(entity: E): Promise<E> {
entity.id = new ObjectId()
return super.create(entity)
}
} And now every Model extends MongoEntity and every Repository extends MongoEntityRepository. Im my case, I'm trying to create a token authentication similar to lb3 with 2 models Profile and AccessToken. access-token.model.ts @model()
export class AccessToken extends MongoEntity {
//Other properties
@belongsTo(() => Profile)
userId: string;
constructor(data?: Partial<AccessToken>) {
super(data);
}
} access-token.repository.ts export class AccessTokenRepository extends DefaultCrudRepository<
AccessToken,
typeof AccessToken.prototype.id
> {
public readonly user: BelongsToAccessor<
Profile,
typeof AccessToken.prototype.userId
>;
async create(token: AccessToken): Promise<AccessToken> {
token.id = jwt.sign({ userId: token.userId }, 'secret')
return super.create(token)
}
constructor(
@inject('datasources.GlarDB') dataSource: GlarDBDataSource,
@repository.getter('ProfileRepository')
profileRepositoryGetter: Getter<ProfileRepository>,
) {
super(AccessToken, dataSource);
this.user = this._createBelongsToAccessorFor(
'userId',
profileRepositoryGetter,
);
}
} profile.controller.ts This is where the AccessToken is created @post('/profiles/login', {
responses: {
'200': {
description: 'Profile model instance',
content: { 'application/json': { 'x-ts-type': Profile } },
},
},
})
async login(
@requestBody() profile: { username: string, password: string }
): Promise<AccessToken> {
const profileExists: Profile | null = await this.profileRepository.findOne({
where: {
username: profile.username,
password: profile.password
}
})
if (!profileExists) throw new HttpErrors.NotFound()
const tokenCreated = new AccessToken({ userId: profileExists.id })
return await this.accessTokenRepository.create(tokenCreated)
} The documents created in Mongo are: {
"_id" : ObjectId("5bd19395171e9d5560960712"),
"username" : "usertest",
"password" : "passtest",
"name" : "nametest",
"email" : "[email protected]"
} But the accessToken.userId is a string: {
"_id" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1YmQwODVhMTk4M2NmNDVhNTAyOGE1NGUiLCJpYXQiOjE1NDA0NjEyNzl9.C-PPE9_WezZU4Wvo5J6Qm4m-GukKQpch7-5cliq9ZeY",
"ttl" : 1209600,
"created" : ISODate("2018-10-25T09:54:39.787Z"),
"userId" : "5bd085a1983cf45a5028a54e"
} I noticed that @marioestradarosa Any help on that subject? Thanks |
Hi @israelglar , thanks for the explanation on the Object ID. I will try to work on an exampled based on your code. I see now what @mrbatista was saying about the Object ID. I see the initial id was created perfectly because its ID property happens to be If the latter is correct, then |
That's exactly the problem. I already tried many things to make the userId an ObjectId like the Id but with no success :/ |
@israelglar set userId as type |
@mrbatista Nice, it works :) I need to set userId as type any and create a new ObjectId on AccessToken creation like this: @belongsTo(() => Profile)
userId: any; // tslint:disable-line access-token.repository.ts async create(token: AccessToken): Promise<AccessToken> {
token.id = jwt.sign({userId: token.userId}, 'secret', {expiresIn: '7d'});
token.userId = new ObjectId(token.userId);
return super.create(token);
} It's a bit of a inconvenience to do this for every model and lose the type on userId, but it's a solution for now. @marioestradarosa You know if there's a possibility to include a ObjectId type support on lb4? |
Yes, that's the original issue all about. Thanks @israelglar for making the example and @mrbatista to help on this. @bajtos already labeled it. I believe that this new type should also be listed when creating the model when mongo DB datasources are selected, or is this ObjectID something already implemented in other document oriented databases? |
+1
AFAIK, ObjectID is MongoDB specific. However, we allow relations across different databases, e.g. a "Customer" model stored in MongoDB can have many "Order" instances stored in MySQL, therefore we should support ObjectID properties also for models stored in different databases. |
You could try adding the Please reference the following issue. |
Working on the TodoList tutorial, I tried to set
to POST the Todo: {
"title": "wine"
} it results in the following document inserted in {
"_id" : ObjectId("5c0ab25ab6db018f62e85fc6"),
"title" : "wine",
"todoListId" : {
"0" : "5",
"1" : "c",
"2" : "0",
"3" : "a",
"4" : "9",
"5" : "3",
"6" : "8",
"7" : "e",
"8" : "f",
"9" : "c",
"10" : "0",
"11" : "c",
"12" : "0",
"13" : "d",
"14" : "7",
"15" : "f",
"16" : "2",
"17" : "1",
"18" : "b",
"19" : "b",
"20" : "c",
"21" : "a",
"22" : "c",
"23" : "7"
}
} Instead I found that the @israelglar workaround works very well, but it must be extended to all methods where the foreign key is involved: For instance here there is the async find(filter?: Filter<Todo> | undefined, options?: AnyObject | undefined): Promise<Todo[]> {
if (filter && filter.where && filter.where.todoListId) {
filter.where.todoListId = new ObjectId(filter.where.todoListId);
}
return super.find(filter, options);
} It can result in writing a lot of (potentially unnecessary) code, I hope that |
@sertal70 did you look at #2085 (comment) ? |
@codejamninja sure I did and really don't understand why the setting As my test proves, to some extent ObjectId data type is already managed by the framework but for some reason it is not exposed up to the model definition section. Maybe the fix could be really simple... |
@sertal70, can you send a link to an example application using |
Sure @codejamninja I uploaded the modified tutorial in a public repo here. Remember to set a valid MongoDB URI in |
Cool, thanks |
@codejamninja did you have the chance to look at my repo? What's your thought about it? |
I did. It seems like a much simpler approach. I actually haven't got a chance to test it yet. I will hopefully get around to it sometime this week. Does the For example, will the following code . . . return await this.todoListRepo.todos(id).find(filter); . . . end up executing async find(filter?: Filter<Todo> | undefined, options?: AnyObject | undefined): Promise<Todo[]> {
if (filter && filter.where && filter.where.todoListId) {
filter.where.todoListId = new ObjectId(filter.where.todoListId);
}
return super.find(filter, options);
} |
Yes, it does. |
import {
DefaultCrudRepository,
BelongsToAccessor,
repository,
} from '@loopback/repository';
import {User, Accesstoken} from '../models';
import {UserRepository} from '../repositories';
import {DbDataSource} from '../datasources';
import {inject, Getter,} from '@loopback/core';
var jwt =require('jwt-decode');
export class AccesstokenRepository extends DefaultCrudRepository<
Accesstoken,
typeof Accesstoken.prototype.id
> {
public readonly user: BelongsToAccessor<
User,
typeof Accesstoken.prototype.user_id
>;
async create(token: Accesstoken): Promise<Accesstoken> {
token.id = jwt.sign({ user_id: token.user_id }, 'secret', {expiresIn: '7d'});
token.user_id = new Object(token.userId);
return super.create(token)
}
constructor(
@inject('datasources.db') dataSource: DbDataSource,
@repository.getter('UserRepository')
userRepositoryGetter: Getter<UserRepository>,
) {
super(Accesstoken, dataSource);
this.user = this.createBelongsToAccessorFor(
'user_id',
userRepositoryGetter,
);
}
} |
please help not genrate token id |
@Shamanpreet please format your code in a code block. It's very hard to read. You can format it in a code block using three backticks.
|
Folks still asking about #2085 . |
|
With the above comment, I'd like to propose the following acceptance criteria: Acceptance Critera
|
ObjectID has been always problematic, see the following threads from LoopBack 1.x/2.x/3.x days: |
In #2085 (comment), I am proposing the following pattern for defining ObjectID properties: @model({
settings: {
strictObjectIDCoercion: true
}
})
export class User extends Entity {
@property({
type: 'string',
id: true,
mongodb: {
dataType: 'ObjectID' // or perhaps 'objectid'?
}
})
id?: string;
// ...
}
The full implementation of AFAICT, this code will not recognize connector-specific property settings at the moment. Hopefully, it should be an easy fix to implement. I would open a PR myself, but don't have enough time for that right now. I think the most time-consuming part is to write good tests for this new behavior. We should test at least |
I am more seduced by your second proposal of using a correct property type. This is far more intuitive and is what I expect from Loopback : make my life easier, not punish me for my database choice. MongoDB is a very common database should enjoy all the fun of LoppBack 💯 Databse popularity, StackOverflow 2018 |
See related PR loopbackio/loopback-connector-mongodb#517 |
A short-term fix has been implemented in the MongoDB connector, the solution is to define your ObjectID properties as Unfortunately, this does not work for the primary key ( We are going investigate further improvements as part of #3720. I am closing this issue as resolved. |
The type to be any |
Is there a support in Loopback 4 for ObjectID type when using a mongoDB database?
The text was updated successfully, but these errors were encountered: