S3Object GraphQL type in AppSync with Lambda datasource? - mongodb

Is the S3Object GraphQL type that is available in AppSync (see Complex objects section in https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app-ios.html) tied to dynamoDB, or could it be used with a Lambda datasource (say one connecting to a mongoDB)?
From the AWS docs linked above...
type Post {
id: ID!
author: String!
title: String
content: String
url: String
ups: Int
downs: Int
file: S3Object
version: Int!
}
type S3Object {
bucket: String!
key: String!
region: String!
}
input S3ObjectInput {
bucket: String!
key: String!
region: String!
localUri: String
mimeType: String
}

I haven't tried this, but you should be able to do what you're looking to accomplish and use a Lambda data source that reads/writes to something else like Mongo or even RDS. AppSync needs the GraphQL types of S3Object and S3ObjectInput along with the fields like bucket and so forth listed above for the client SDKs and codegen to properly build out objects, however the S3Link functionality is done in the resolver itself both for reading and writing. You could move this to your logic layer in a Lambda.
If you look at https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html#dynamodb-helpers-in-util-dynamodb you will see the mapping function signatures and output:
$util.dynamodb.toS3Object(String key, String bucket, String region) : Map
$util.dynamodb.toS3ObjectJson(String key, String bucket, String region) : String
$util.dynamodb.toS3Object(String key, String bucket, String region, String version) : Map
$util.dynamodb.toS3ObjectJson(String key, String bucket, String region, String version) : String
$util.dynamodb.fromS3ObjectJson(String) : Map
So if you want to move this logic to write/read into a Lambda that's completely possible. If you standup this sample you'll be able to reverse engineer it: https://github.com/aws-samples/aws-amplify-graphql

Related

panic: reflect: call of reflect.Value.Interface on zero Value on GORM .Create()

I'm new to go and Backend and I'm Trying to make many-to-many relation between tables. I used this repo to make model:https://github.com/harranali/gorm-relationships-examples/tree/main/many-to-many
I Used GORM with postgresql.
My model:
type Book struct {
gorm.Model
Title string `json:"title"`
Author string `json:"author"`
Description string `json:"description"`
Category string `json:"Category"`
Publisher string `json:"publisher"`
AuthorsCard []*AuthorsCard `gorm:"many-to-many:book_authorscard;" json:"authorscard"`
}
type AuthorsCard struct {
gorm.Model
Name string `json:"name"`
Age int `json:"age"`
YearOfBirth int `json:"year"`
Biography string `json:"biography"`
}
After connecting to database and AutoMigrating:
func init() {
config.Connect()
db = config.GetDB()
db.AutoMigrate(&models.Book{}, &models.AuthorsCard{})
}
I've created Function to see how that relation works:
func TestCreate() {
var AuthorsCard = []models.AuthorsCard{
{
Age: 23,
Name: "test",
YearOfBirth: 1999,
Biography: "23fdgsdddTEST",
},
}
db.Create(&AuthorsCard)
var testbook = models.Book{
Title: "Test",
Author: "tst",
Description: "something",
}
db.Create(&testbook)
db.Model(&testbook).Association("AuthorsCard").Append(&AuthorsCard)
}
But got This Error:
panic: reflect: call of reflect.Value.Interface on zero Value [recovered]
panic: reflect: call of reflect.Value.Interface on zero Value
How can I deal with this "Null" problem and make proper relation?
UPD: The First part of a problem was connected to a version of GORM, After I changed old version(github.com/jinzhu/gorm v1.9.16) to new version (gorm.io/gorm v1.23.6) the problem with reflect Error gone.
but now, when I want to create new book, I get this Error:
/go/pkg/mod/gorm.io/driver/postgres#v1.3.7/migrator.go:119 ERROR: there is no unique constraint matching given keys for referenced table "authors_cards" (SQLSTATE 42830)
[28.440ms] [rows:0] CREATE TABLE "book_authorscard" ("book_id" bigint,"authors_card_id" bigint,PRIMARY KEY ("book_id","authors_card_id"),CONSTRAINT "fk_book_authorscard_authors_card" FOREIGN KEY ("authors_card_id") REFERENCES "authors_cards"("id"),CONSTRAINT "fk_book_authorscard_book" FOREIGN KEY ("book_id") REFERENCES "books"("id"))
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
UPD 2:
I decided to make a Migrator().DropTable(). That's kinda worked, and all Errors have gone. But still I get "authorscard": null as a response.
By reading the release note of Gorm v2 (https://gorm.io/docs/v2_release_note.html), I think that you are trying to use v2 feature with an old version (<v2). Try to use Gorm latest version.

Dql mutation add duplicate records

What I want to do
Prevent the dql mutation to add duplicates records
What I did
I add a graphql schema:
type Product {
id: ID!
name: String! #id #dgraph(pred: "Product.name")
slug: String! #id #dgraph(pred: "Product.slug")
image: String #dgraph(pred: "Product.image")
created_at: DateTime! #dgraph(pred: "Product.created_at")
updated_at: DateTime! #dgraph(pred: "Product.updated_at")
}
the above graphql schema has generated the bellow DQL schema:
<Product.created_at>: datetime .
<Product.image>: string .
<Product.name>: string #index(hash) #upsert .
<Product.slug>: string #index(hash) #upsert .
<Product.updated_at>: datetime .
<dgraph.drop.op>: string .
<dgraph.graphql.p_query>: string #index(sha256) .
<dgraph.graphql.schema>: string .
<dgraph.graphql.xid>: string #index(exact) #upsert .
type <Product> {
Product.name
Product.slug
Product.image
Product.created_at
Product.updated_at
}
type <dgraph.graphql> {
dgraph.graphql.schema
dgraph.graphql.xid
}
type <dgraph.graphql.persisted_query> {
dgraph.graphql.p_query
}
I run a mutation to add some data using: https://github.com/dgraph-io/dgo#running-a-mutation.
But it does not respect the #id added to the schema to some fields like "slug" and "name".
Using the graphql mutation this is working and respect the uniqueness by returning an error:"message": "couldn't rewrite mutation addProduct because failed to rewrite mutation payload because id aaaa already exists for field name inside type Product"
dgraph version v21.03.2
In Dql you have to handle this by your own using Upsert Block.
Or if you don't want the power of dql you can use the graphql which handle this stuff automatically.

AWS Amplify and GraphQL Interfaces

How would you deal with interfaces and using them for connections in a data model using the AWS Amplify Model Transforms?
interface User #model {
id: ID
email: String
created: AWSTimestamp
}
type ActiveUser implements User {
id: ID
first: String
last: String
email: String
created: AWSTimestamp
}
type InvitedUser implements User {
id: ID
email: String
created: AWSTimestamp
invitedBy: String
}
type Team #model {
users: [User] #connection
}
It seems like my choices are to put #model on the types but then I get separate Dynamo tables and queries on the Query once amplify update api is run.
Can the transformer support interfaces as documented here: https://docs.aws.amazon.com/appsync/latest/devguide/interfaces-and-unions.html
I also found some support tickets, but was wondering if there was anything out there that enabled this feature. Here are the support tickets I found:
https://github.com/aws-amplify/amplify-cli/issues/1037
https://github.com/aws-amplify/amplify-cli/issues/202
You only use #connection to link two databases together (which must be made from type and not interface), so if you don't want to do that then just get rid of the #connection and the Team database will simply have users be of type [User]. I am not entirely what you want to do but I would do something like:
type User #model {
id: ID
first: String!
last: String!
email: String!
created: AWSTimestamp
isActive: boolean
invitedBy: String
team: Team #connection(name: "UserTeamLink")
}
type Team #model {
users: [User!] #connection(name: "UserTeamLink")
}
Where the fields first, last, and email are required when creating a new user, and you can distinguish between an active user with a boolean, and when you query the User database it returns the Team item from the Team database as well (I am guessing you want other fields like team name, etc.?), so when you create a Team object you pass in the teamUserId (not shown below but created when using amplify) that will allow you to attach a newly created Team to an existing user or group of users.
I think you could keep the common fields in User, and extra info in separate type. Not sure if this is the best practice, but it should work for this scenario
enum UserType {
ACTIVE
INVITED
}
type User #model #key(name:"byTeam", fields:["teamID"]){
id: ID!
teamID: ID!
email: String
created: AWSTimestamp
type: UserType
activeUserInfo: ActiveUserInfo #connection(fields:["id"])
invitedUserInfo: InvitedUserInfo #connection(fields:["id"])
}
type ActiveUserInfo #key(fields:["userID"]){
userID: ID!
first: String
last: String
}
type InvitedUserInfo #key(fields:["userID"]){
userID: ID!
invitedBy: String
}
type Team #model {
id:ID!
users: [User!] #connection(keyName:"byTeam", fields:["id"])
}

TypeOrm findOne throws 'Argument passed in must be a single String of 12 bytes or a string of 24 hex characters'

Hi I'm using NestJs with TypeOrm and currently connected to a MongoDB (but I want to be independent because most customers use MSSQL and Postgres databases).
Basically I want to store a key/value pair to the database. Both are of type String. This is the mapping entity holding the pair
#Entity()
export class Mapping extends BaseEntity {
#PrimaryColumn()
key: string;
#Column()
value: string;
}
The repository is currently empty
#EntityRepository(Mapping)
export class MappingsRepository extends Repository<Mapping> {}
In my service file I try to find a value by its key
public async getValueByKey(key: string): Promise<string> {
const mapping: Mapping = await this.mappingsRepository.findOne(key);
if (!mapping) {
throw new NotFoundException(`Key ${key} does not exist`);
}
return mapping.value;
}
Unfortunately the findOne function always throws this error when passing in a key
Argument passed in must be a single String of 12 bytes or a string of
24 hex characters
How can I fix this so that I can pass in any keys of type String? The parameter key is defined, I tried it with dummy values like f or ffsdagsdgfdg
Can you try
await this.mappingsRepository.findOne({key});
My guess it that typeorm doesn't understand primary key other than id
findOne for mongo means it will search document by property _id and not your key
because, mongo, by default, always add this _id as unique validator for each document
so, you should provide a query to that method like it is defined in itssajan answer
but i think your use case of TypeORM is not the best, because it is made to work with SQL dbs, and not NoSQL dbs

Defining an API with swagger: GET call that uses JSON in parameters

I am trying to create a proper, REST API, and document it with Swagger (2.0).
So, I have an API call that is a query, ie, it makes no changes and doesn't create anything (idempotent and safe). But it requires passing in a complex JSON parameter (list of items, 2 or 3 sets of addresses, etc). So I'm doing a GET with a parameter thats URL encoded JSON. That seems like the proper way to do it.
I see so often API's like this where they do it as a POST for this reason, but that's an incorrect use of the POST verb.
I'm seeing lots of swagger API's that do this...
I can't figure out if there's a way to do a proper rest API with Swagger, using a JSON parameter. You can define the parameter as a string, of course, and pass your encoded JSON into it, but then the swagger tooling doesn't understand that there's a schema/definition for it.
Is swagger not able to properly document this kind of call?
OpenAPI 2.0 (Swagger 2.0)
OpenAPI 2.0 does not support objects in query strings, it only supports primitive values and arrays of primitives. The most you can do is define your parameter as type: string, add an example of a JSON value, and use description to document the JSON object structure.
swagger: '2.0'
...
paths:
/something:
get:
parameters:
- in: query
name: params
required: true
description: A JSON object with the `id` and `name` properties
type: string
example: '{"id":4,"name":"foo"}'
OpenAPI 3.x
JSON in query string can be described using OpenAPI 3.x. In OAS 3, query parameters can be primitives, arrays as well as objects, and you can specify how these parameters should be serialized – flattened into key=value pairs, encoded as a JSON string, and so on.
For query parameters that contain a JSON string, use the content keyword to define a schema for the JSON data:
openapi: 3.0.1
...
paths:
/something:
get:
parameters:
- in: query
name: params
required: true
# Parameter is an object that should be serialized as JSON
content:
application/json:
schema:
type: object
properties:
id:
type: integer
name:
type: string
This corresponds to the following GET request (before URL encoding):
GET /something?params={"id":4,"name":"foo"}
or after URL encoding:
GET /something?params=%7B%22id%3A4%2C%22name%22%3A%22foo%22%7D
Note for Swagger UI users:
Parameters with content are supported in Swagger UI 3.23.8+ and Swagger Editor 3.6.34+.
Workaround for earlier versions of UI/Editor:
Define the parameter as just type: string and add an example of the JSON data. You lose the ability to describe the JSON schema for the query string, but "try it out" will work.
parameters:
- in: query
name: params
required: true
schema:
type: string # <-------
example: '{"id":4,"name":"foo"}' # <-------
For .Net and Swashbuckle (tested on 3.0)
I have a generic class JsonModelBinder that implements IModelBinder interface. The class is used like this:
public IActionResult SomeAction(
[FromRoute] int id,
[FromQuery][ModelBinder(BinderType = typeof(JsonModelBinder<SomeModel>))] SomeModelquery query) => {}
I have created Operation filter that does the following:
Removes parameters created by Swashbuckle from properties of my model
Add query parameter of type string
As a result in Swagger I have a text field where I can insert json and test requests
public class JsonModelBinderOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (operation.Parameters == null || context.ApiDescription.HttpMethod != HttpMethod.Get.ToString())
return;
//Find json parameters
var jsonGetParameters = context.ApiDescription.ActionDescriptor.Parameters.Cast<ControllerParameterDescriptor>()
.Where(p => p.ParameterInfo.CustomAttributes.Any(c => c.AttributeType == typeof(ModelBinderAttribute) && c.NamedArguments.Any(IsJsonModelBinderType))).ToArray();
if (jsonGetParameters.Length > 0)
{
//Select parameters names created by Swagger from json parameters
var removeParamNames = new HashSet<string>(context.ApiDescription.ParameterDescriptions.Where(d => jsonGetParameters.Any(p => p.Name == d.ParameterDescriptor.Name)).Select(p => p.Name));
//Create new Swagger parameters from json parameters
var newParams = jsonGetParameters.Select(p => new NonBodyParameter()
{
In = "query",
Name = p.Name,
Type = "string",
Description = "Json representation of " + p.ParameterType.Name
});
//Remove wrong parameters and add new parameters
operation.Parameters = operation.Parameters.Where(p => p.In != "query" || !removeParamNames.Contains(p.Name)).Concat(newParams).ToList();
}
}
private static bool IsJsonModelBinderType(CustomAttributeNamedArgument arg)
{
var t = arg.TypedValue.Value as Type;
return t != null && t.GetGenericTypeDefinition().IsAssignableFrom(typeof(JsonModelBinder<>));
}
}
Notes:
I use IsAssignableFrom because I have classes derived from JsonModelBinder. You can omit it if you don't inherit
You can also omit GetGenericTypeDefinition if your binder is not generic
This solution doesn't check for parameter name collision, though you should never have it if the API made with common sense