AWS Amplify Flutter one to many relationship - graphql create - not working? - flutter

I am new to AWS Amplify and GraphQL API. I created data models using Amplify Studio. Here is the schema.graphql:
type Language #model #auth(rules: [{allow: public}]) {
id: ID!
name: String
fluency: String
rpm: Float
translatorID: ID #index(name: "byTranslator")
}
enum TranslatorTime {
FULLTIME
FREETIME
PARTTIME
}
type Translator #model #auth(rules: [{allow: public}]) {
id: ID!
userId: String
time: TranslatorTime
Languages: [Language] #hasMany(indexName: "byTranslator", fields: ["id"])
}
I am using graphql api on flutter app to create items in translator model, here is the code i tried:
Future<void> addTranslator(Translator translator) async {
try {
final request = ModelMutations.create(translator);
final response = await Amplify.API.mutate(request: request).response;
final createdTranslator = response.data;
if (createdTranslator == null) {
safePrint('errors: ${response.errors}');
return;
}
safePrint('Mutation result: $createdTranslator');
} on ApiException catch (e) {
safePrint('Mutation failed: $e');
}
}
But the problem is when i create translator item, the list of languages is not saved in the database, since the translator has one-to-many relation with language, its not working, in both tables translator and language. I am creating list of language and passing to the language model. The mutation result is:
Mutation result: Translator {id=156a3fd4-ae09-4579-977c-0ca4bb205aca, userId=7d8d034e-d13c-4f53-87c4-4f4bb9adfe03, time=PARTTIME, createdAt=2023-02-06T10:05:52.329000000Z, updatedAt=2023-02-06T10:05:52.329000000Z}
it does not have Languages. Tried with translatorId and without it.

Related

Amplify Flutter returns null when creating a manyToMany item

I am making a chat application which has two tables like below:
type User #model #auth(rules: [{ allow: public, operations: [create, read], provider: iam }]) {
id: ID!
# relation
rooms: [Room] #manyToMany(relationName: "UserRoom")
}
type Room #model #auth(rules: [{ allow: public, operations: [create, read], provider: iam }]) {
id: ID!
# relation
users: [User] #manyToMany(relationName: "UserRoom")
}
and I wrote a code to 'enter a chat room' like below:
Future<UserRoom?> join(Room room, User user) async {
try {
UserRoom userroom = UserRoom(room: room, user: user);
final request = ModelMutations.create(userroom);
final response = await Amplify.API.mutate(request: request).response;
return response.data;
} catch (error) {
safePrint("Failed on Creating UserRoom: $error");
}
return null;
}
however the above function returns null and creates nothing in my DynamoDB.
I've tried Amplify DataStore, but it does not work as I expected.
(e.g. I need to create an item simultaneously regardless of network condition but it take longer than I expected.)
I am expecting the joined row is successfully created.

Why does Amplify Datastore return null in objects having #hasmany relationships?

I'm new here and I hope to find a solution to this problem. I am new to Flutter and AWS Amplify as well. Here it goes.
I created a very simple schema using Admin UI. The schema has two models, Order and Item. Order contains many Item(s).
I was surprised that call to get OrderItems() returns null. I read a post elsewhere that said #hasmany relationships do not work with with AWS Amplify and a second query was needed to retrieve #hasmany fields.
The CLI generated models seem to support #hasmany relationships.
Here is the simple schema generated by Amplify:
type Items #model #auth(rules: [{allow: public}]) {
id: ID!
productName: String
quantity: String
orderID: ID #index(name: "byOrder")
}
type Order #model #auth(rules: [{allow: public}]) {
id: ID!
Customer: String
OrderItems: [Items] #hasMany(indexName: "byOrder", fields: ["id"])
}
Amplify generated the corresponding models in my project. For brevity, I will include selected parts of the CLI generated code for the Order object. I don't believe the generated models can be modified. Moreover, If the schema is changed, the model dart project files are overwritten.
#override
String getId() {
return id;
}
String? get Customer {
return _Customer;
}
// Returns null
List<Items>? get OrderItems {
return _OrderItems;
}
It appears that ['OrderItems'] should be populate in Order.fromJson.
Order.fromJson(Map<String, dynamic> json)
: id = json['id'],
_Customer = json['Customer'],
_OrderItems = json['OrderItems'] is List
? (json['OrderItems'] as List)
.where((e) => e?['serializedData'] != null)
.map((e) => Items.fromJson(new Map<String, dynamic>.from(e['serializedData'])))
.toList()
: null;
What am I missing? What is the purpose of this code?
The remainder of the CLI generated code seems to support CRUD operations on both Order and Item.
Map<String, dynamic> toJson() => {
'id': id, 'Customer': _Customer, 'OrderItems': _OrderItems?.map((Items? e) => e?.toJson()).toList()
};
static final QueryField ID = QueryField(fieldName: "order.id");
static final QueryField CUSTOMER = QueryField(fieldName: "Customer");
static final QueryField ORDERITEMS = QueryField(
fieldName: "OrderItems",
fieldType: ModelFieldType(ModelFieldTypeEnum.model, ofModelName: (Items).toString()));
static var schema = Model.defineSchema(define: (ModelSchemaDefinition modelSchemaDefinition) {
modelSchemaDefinition.name = "Order";
modelSchemaDefinition.pluralName = "Orders";
modelSchemaDefinition.authRules = [
AuthRule(
authStrategy: AuthStrategy.PUBLIC,
operations: [
ModelOperation.CREATE,
ModelOperation.UPDATE,
ModelOperation.DELETE,
ModelOperation.READ
])
];
modelSchemaDefinition.addField(ModelFieldDefinition.id());
modelSchemaDefinition.addField(ModelFieldDefinition.field(
key: Order.CUSTOMER,
isRequired: false,
ofType: ModelFieldType(ModelFieldTypeEnum.string)
));
modelSchemaDefinition.addField(ModelFieldDefinition.hasMany(
key: Order.ORDERITEMS,
isRequired: false,
ofModelName: (Items).toString(),
associatedKey: Items.ORDERID
));
});
}
class _OrderModelType extends ModelType<Order> {
const _OrderModelType();
#override
Order fromJson(Map<String, dynamic> jsonData) {
return Order.fromJson(jsonData);
}
}
It appears that all the plumbing is in place but the water is turned off. Like I stated, I'm new at this so...
Is this a bug?
One last thing...
ModelProvider.dart throw this compile time error.
Missing concrete implementations of 'getter ModelProviderInterface.customTypeSchemas' and 'setter ModelProviderInterface.customTypeSchemas'.
Try implementing the missing methods, or make the class abstract
Whenever ModelProvider.dart is regenerated I have to add the following line of code.
#override
List<ModelSchema> customTypeSchemas =[];
Thanks in advance
Apparently, #hasOne and #hasMany directives do not support referencing a model which then references the initial model via #hasOne or #hasMany if DataStore is enabled.
Details: Has-One-Relationship

Unknown argument error when creating record

I'm encountering some interesting behaviour when using Prisma ORM. It is related to Prisma's generated types, and I've been skimming the docs trying to find out more, but there doesn't seem to be much info about generated types in there (please correct me if I'm mistaken). Here's the behaviour:
Say I have a model with two 1-1 relations (Profile in the example below):
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int #id #default(autoincrement())
name String
profile Profile?
}
model Profile {
id Int #id #default(autoincrement())
name String
userId Int?
user User? #relation(fields: [userId], references: [id])
photoId Int?
photo Photo? #relation(fields: [photoId], references: [id])
}
model Photo {
id Int #id #default(autoincrement())
url String
profile Profile?
}
The following code works when creating a new profile:
const user = await prisma.user.create({ data: { name: "TestUser" } });
const profile = await prisma.profile.create({
data: {
name: "TestProfile",
user: { connect: { id: user.id } },
photo: { create: { url: "http://example.com/img" } },
},
});
... but this fails with an error:
const user = await prisma.user.create({ data: { name: "TestUser" } });
const profile = await prisma.profile.create({
data: {
name: "TestProfile",
userId: user.id,
photo: { create: { url: "http://example.com/img" } },
},
});
The error is:
Unknown arg userId in data.userId for type ProfileCreateInput. Did you mean user? Available args:
type ProfileCreateInput {
  name: String
  user?: UserCreateNestedOneWithoutProfileInput
  photo?: PhotoCreateNestedOneWithoutProfileInput
}
Why is the second create-profile code invalid?
Prisma essentially generates two type definitions for a create query. This is implemented with a XOR type, which ensures that only one definition out of two is fully specified and passed to the query:
export type ProfileCreateArgs = {
/* ... */
data: XOR<ProfileCreateInput, ProfileUncheckedCreateInput>;
}
The definitions are called checked and unchecked, the former using nested fields and the latter using raw ids:
export type ProfileCreateInput = {
id?: number;
/* ... */
user?: UserCreateNestedOneWithoutProfileInput;
photo?: PhotoCreateNestedOneWithoutProfileInput;
}
export type ProfileUncheckedCreateInput = {
id?: number;
/* ... */
userId?: number;
photoId?: number;
}
Which basically means that you either provide all references as connect, create etc. relations (checked) or as raw ids (unchecked). You cannot mix the styles, this is not supported.

MongoDB and TypeScript: Decouple a domain entity's id type from MongoDB's ObjectID

Inside my MongoDB repositories, entities have an _id: ObjectID type to be handled properly. However, I would like my domain entities to have a simple id: string attribute to avoid any dependencies on any database or framework. The solution I came up with so far looks as follows:
export interface Book {
id: string;
title: string;
}
// A MongodbEntity<Book> would now have an _id instead of its string id
export type MongodbEntity<T extends { id: string; }> = Omit<T, 'id'> & { _id: ObjectID; };
In my repository this would work:
async findOneById(id: string): Promise<Book | null> {
const res = await this.collection.findOneById({_id: new ObjectId(id)});
return res ? toBook(res) : null;
}
function toBook(dbBook: MongodbEntity<Book>): Book {
const {_id, ...rest} = dbBook;
return {...rest, id: _id.toHexString() };
}
What doesn't work is to make this behavior generic. A converter function like this:
function toDomainEntity<T extends {id: string}>(dbEntity: MongoDbEntity<T>): T {
const {_id, ...rest} = dbEntity;
return {...rest, id: _id.toHexString() };
}
leads to an error described here.
What I am looking for is either a working solution for the generic toDomainEntity function or a different (generic) approach that would let me decouple my domain entity types from MongoDB's _id: ObjectID type.

How to query in GraphQL with Sequelize a many to many association? Receiving null error

I have many to many association, models are Decks and Tag. I am able to console.log a JSON string with both objects, but I am unsure how to set my schema and resolver to return both in one query together, and currently receiving null. I'm using GraphQL with Sequelize on an Apollo server, with PostgreSQL as the db. What's the proper method to query this relationship in GraphQL, I presume I am not returning the data properly for GraphQL to read it?
Models
Deck.belongsToMany(models.Tag, {
through: models.DeckTag,
onDelete: "CASCADE"
});
Tag.associate = models => {
Tag.belongsToMany(models.Deck, {
through: models.DeckTag,
onDelete: "CASCADE"
});
Schema for Decks
export default gql`
extend type Query {
decks(cursor: String, limit: Int): DeckConnection!
deck(id: ID, deckname: String): Deck!
decksWithTags: [Deck!]!
}
extend type Mutation {
createDeck(deckname: String!, description: String!): Deck!
deleteDeck(id: ID!): Boolean!
}
type DeckConnection {
edges: [Deck!]!
pageInfo: DeckPageInfo!
}
type DeckPageInfo {
hasNextPage: Boolean!
endCursor: String!
}
type Deck {
id: ID!
description: String!
createdAt: Date!
user: User!
cards: [Card!]
}
`;
Resolver in question
decksWithTags: async (parent, args, { models }) => {
return await models.Deck.findAll({
include: [models.Tag]
}).then(tags => {
console.log(JSON.stringify(tags)); //able to console.log correctly
});
},
Shortened sample Console.logged JSON String
[
{
"id":1,
"deckname":"50 words in Chinese",
"description":"Prepare for your immigration interview",
***
"userId":1,
"tags":[
{
"id":1,
"tagname":"Chinese",
***
"decktag":{
***
"deckId":1,
"tagId":1
}
},
{
"id":2,
***
{
"id":2,
"deckname":"English",
***
I expect to get a result in GraphQL playground that looks similar to the JSON string.