I have decided to have an interaction collection to hold the interactions between members of my App. Who blocked who, who liked who, who followed who, etc.
The schema will look something like this:
{
mem_1: user_id,
mem_2: user_id,
mem_1_blocked_mem_2: "boolean",
mem_2_blocked_mem_1: "boolean",
etc...
}
The problem is, how is it decided, which member should be in the mem_1 field and which should be in the mem_2 field?
And then, when it comes to queries, how do I know which field is who?
This might be the worst design I have seen ever.
I would suggest this:
{
userId: "user1",
blocked: ["user2", "user3"],
liked: ["user5"]
},
{
userId: "user2",
blocked: ["user3"],
liked: ["user1"]
}
If you like to speed up your design and if you don't care about redundancies, you can extend also by this:
{
userId: "user1",
blocked: ["user2", "user3"],
liked: ["user5"],
blocked_by: [],
liked_by: ["user5", "user2"]
},
{
userId: "user2",
blocked: ["user3"],
liked: ["user1"]
blocked_by: ["user1"],
liked_by: []
}
Related
There really isn't enough documentation on this either in the AWS docs or in the Github, so hopefully someone here has tackled a similar issue.
I have a react app with backend api hosted on AWS, using appsync, dynamoDB, and cognito-user-pools. My IAM policies are set up to allow unauth users read-only permission to some public tables. I tried the public api key but that didn't do anything. I'm trying to get the IAM unauth role permissions set up but even when I experimentally added literally every service and every action to the unauth role, I still get "no current user" when attempting to make the API call without logging in.
Use case is for public author pages, where information about an author along with their currently available books is listed. Users should not have to sign in to see this page, an author should be able to drop a link to the page to anyone, whether they have a login for the app or not.
This is my graphql schema for the relevant types, it gets no errors:
type PublicBook #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byPublicWorld", fields: ["publicWorldId", "indexOrder"])
#key(name:"byPublicSeries", fields: ["publicSeriesId", "indexOrder"]){
id: ID!
publicWorldId: ID
publicSeriesId: ID
indexOrder: Int!
cover: FileObject #connection
description: String
amazon: String
ibooks: String
smashwords: String
kobo: String
goodreads: String
audible: String
barnesnoble: String
sample: String
}
type PublicSeries #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byPublicWorld", fields: ["publicWorldId", "indexOrder"]){
id: ID!
publicWorldId: ID!
indexOrder: Int!
logo: FileObject #connection
description: String
genre: String
books: [PublicBook]#connection(keyName:"byPublicSeries", fields: ["id"])
}
type PublicWorld #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byAuthorPage", fields: ["authorPageId", "indexOrder"]){
id: ID!
authorPageId: ID!
logo: FileObject #connection
description: String
genre: String
indexOrder: Int!
series: [PublicSeries]#connection(keyName:"byPublicWorld", fields: ["id"])
books: [PublicBook]#connection(keyName:"byPublicWorld", fields: ["id"])
}
type AuthorPage #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byPenName", fields: ["penId"])
#key(name:"byPenDisplayName", fields: ["penDisplayName"], queryField: "authorPageByPen"){
id: ID!
authorName: String
penDisplayName: String
penId: ID!
bio: String
photo: FileObject #connection
logo: FileObject #connection
penFBProfile: String
penFBGroup: String
penFBPage: String
penTwitter: String
penInstagram: String
penAmazon: String
penWebsite: String
penNewsletter: String
penGoodreads: String
penPatreon: String
posts: [AuthorPost]#connection(keyName:"byAuthorPage", fields: ["id"])
worlds: [PublicWorld]#connection(keyName:"byAuthorPage", fields: ["id"])
}
type AuthorPost #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byAuthorPage", fields: ["authorPageId", "timeCreated"]){
id: ID!
authorPageId: ID!
timeCreated: AWSTimestamp!
content: String!
title: String!
subtitle: String
type: PostType!
}
Each of these types is set to owner/cognito permissions for creating, updating, and deleting, and then there is a public auth using iam to read. Seems straight forward enough.
The main type here is Author page, and I have the query set up to pull all the connected relevant cascading information. When logged in, this works fine and shows an author page with all the bits and bobs:
export const authorPageByPen = /* GraphQL */ `
query AuthorPageByPen(
$penDisplayName: String
$sortDirection: ModelSortDirection
$filter: ModelAuthorPageFilterInput
$limit: Int
$nextToken: String
) {
authorPageByPen(
penDisplayName: $penDisplayName
sortDirection: $sortDirection
filter: $filter
limit: $limit
nextToken: $nextToken
) {
items {
id
authorName
penDisplayName
penId
bio
photo {
location
}
logo {
location
}
penFBProfile
penFBGroup
penFBPage
penTwitter
penInstagram
penAmazon
penWebsite
penNewsletter
penGoodreads
penPatreon
posts {
nextToken
startedAt
}
worlds {
nextToken
startedAt
}
_version
_deleted
_lastChangedAt
createdAt
updatedAt
owner
}
nextToken
startedAt
}
}
`;
On the page itself (although in production this just happens at app.js and persists throughout the app), I'm pulling current credentials and logging them to make sure that some kind of IAM identity is being created, and it appears to be:
accessKeyId: "BUNCHANUMBERSKEY"
authenticated: false
expiration: Thu Mar 04 2021 13:18:04 GMT-0700 (Mountain Standard Time) {}
identityId: "us-west-2:48cd766c-4854-4cc6-811a-f82127670041"
secretAccessKey: "SecretKeyBunchanumbers"
sessionToken:"xxxxxbunchanumbers"
That identityId on line 4 is present in my identity pool as an unauth identity, so it is getting back to the pool, which seems to be what's supposed to happen.
So, this identity pool has two roles associated with it, which is standard: auth and unauth, and my Unauthenticated Identities Setting has the box for Enable Access to Unauthenticated Identities checked.
In my unauth role, I've got the following as the inline policy json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"appsync:GraphQL"
],
"Resource": [
"arn:aws:appsync:us-west-2:MyAccountID:apis/MyAppsyncApiId/types/Mutation/fields/authorPageByPen"
]
}
]
}
I wasn't sure if this needed to be mutation, or query, or what, so I've tried them all. I tried them in combination with 'fields' and with 'index', I've tried writing the JSON, and adding the policies from the inline editor, which gives me the following which also does not work:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "appsync:GraphQL",
"Resource": [
"arn:aws:appsync:us-west-2:MyAccountID:apis/MyAppSyncAPIId/types/AuthorPage/fields/authorPageByPen",
"arn:aws:appsync:us-west-2:MyAccountID:apis/MyAppSyncAPIID"
]
}
]
}
What bit am I missing here? I could understand getting some error about not being allowed to access a resource, but the only error that logs is No Current User, and that happens immediately after the log showing the user.
Update:
Running the query from the Appsync console works fine with IAM and no logged in user. In the page itself, I'm using the following function to call the author page (I'm using routes):
const pullAuthorPage = async () => {
try{
const authorPageData = await API.graphql(graphqlOperation(authorPageByPen, { penDisplayName: props.match.params.id.toLowerCase() }))
console.log(JSON.stringify(authorPageData, null, 2));
setState({...authorPageData.data.authorPageByPen.items[0]})
} catch (error) {
console.log(error);
}
}
What I thought would happen with this is that if there is no authenticated user logged in, this will run using the unauth user credentials. Is that not the case? And if so, how should I change it?
This is not an authentication error, write is enabled on the database rules.
My cloud Firestore database looks like the picture below.
There is a COLLECTION called colA, inside it there is a DOCUMENT called docA, and inside it there are some fields (strings) stored.
On Postman, if I do GET https://firestore.googleapis.com/v1/projects/eletronica-ab6b1/databases/(default)/documents/colA/docA, I do receive the following answer, and it is correct:
{
"name": "projects/eletronica-ab6b1/databases/(default)/documents/colA/docA",
"fields": {
"fieldB": {
"stringValue": "ABCD"
},
"fieldA": {
"stringValue": "888"
}
},
"createTime": "2020-01-31T16:48:26.859181Z",
"updateTime": "2020-02-05T19:21:49.654340Z"
}
Now, when I try to write a new field (fieldC) via POST https://firestore.googleapis.com/v1/projects/eletronica-ab6b1/databases/(default)/documents/colA/docA, with JSON content:
{
"name": "projects/eletronica-ab6b1/databases/(default)/documents/colA/docA",
"fields": {
"fieldC": {
"stringValue": "1000"
}
}
}
After SEND, I receive this:
{
"error": {
"code": 400,
"message": "Document parent name \"projects/eletronica-ab6b1/databases/(default)/documents/colA\" lacks \"/\" at index 60.",
"status": "INVALID_ARGUMENT"
}
}
What I'm doing wrong? I really would like to write strings there via REST API.
Regards.
Updating a document is done with a PATCH request, according to the [reference documentation).
A POST request is used to create a new document in a collection, which probably explains the error you get: you're pointing to a document, but POST expects a collection path.
I am currently trying to figure out how to setup my Backand app and its REST API. This question is related to question: Backand deep querying. However, I was hoping that I could get some best practice code examples on how to perform server side code to perform a loop and create a JSON responds with the following criteria:
I want to be able to make a REST request to Backand and get one data object back that has manipulated/merged two data objects from my database.
I have an object called "media" and another named "users". Obviously, users contain user information and media contains information on a picture that the user has uploaded. The two objects are related by the userId and by collection set in Backand. I want to make a GET request that responds with a JSON object with all pictures and a nested user object on each picture object that contains the related user information. I know that I get back "relatedObjects", and I could then make some manipulation on the client side, but I am hoping that there is another easier way to do this from the Backand administration system either on server side code or as a query.
So, my question is, what's the best way to produce a REST call that responds a database object with nested related data object through Backand?
Here's the object models (shorten for clarity):
User object model as set up in Backand
{
"name": "users",
"fields": {
"media": {
"collection": "media",
"via": "user"
},
"email": {
"type": "string"
},
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
}
} }
Media object model as set up in Backand
{
"name": "media",
"fields": {
"description": {
"type": "string"
},
"thumbnail": {
"type": "string"
},
"fullImage": {
"type": "string"
},
"user": {
"object": "users"
}
}}
Final JSON response that I am looking for:
{
description: 'Blah',
thumbnail: 'someImageUrl.jpg',
fullImage: 'someImageUrl.jpg',
user: {
firstName: 'John'
lastName: 'Smith'
email: 'john#smith.com'
}
}
Just in case anybody else comes across this, I chose to do it with server-side javascript code, since my backend, SQL and NoSQL query skills are very weak. I'm guessing a noSQL query would probably be better in terms of performance. And I would still like to see how it could be done in noSQL. Anyway my server-side javascript code in a Backand action does the job. Here it is:
/* globals
$http - Service for AJAX calls
CONSTS - CONSTS.apiUrl for Backands API URL
Config - Global Configuration
socket - Send realtime database communication
files - file handler, performs upload and delete of files
request - the current http request
*/
'use strict';
function backandCallback(userInput, dbRow, parameters, userProfile) {
var response = [];
var request =
$http({
method: "GET",
url: CONSTS.apiUrl + "/1/objects/media",
headers: {"Authorization": userProfile.token},
params: {
exclude: 'metadata',
deep: true
}
});
var object = request.data;
var related = request.relatedObjects.users;
for (media in object) {
if (object.hasOwnProperty(media)) {
for (user in related) {
if (object[media].user == related[user].id) {
response.push({
id: object[media].id,
thumbnailUrl: object[media].thumbnail,
description: object[media].description,
fullName: related[user].firstName + ' ' + related[user].lastName,
email: related[user].email
});
}
}
}
}
return response;
}
I'm using the Mean Stack and trying to get a comment in comment feature to work for a blog post.
Where i'm getting stuck is trying to get from angular to mongo to update a comment for a given blog and a comment for a comment. I basically don't know how to get angular/express/mongoose to use the a subdocument id or nested subdocument id to update the parent blog or comment.
What I have managed to do so far is:
Create my schema -
var mongoose = require('mongoose'), Schema = mongoose.Schema;
var childSchema = new Schema;
childSchema.add({
firstName: 'string',
lastName: 'string',
comment: 'string',
children: [childSchema]
});
var parentSchema = new Schema({
firstName: 'string',
lastName: 'string',
blog: 'string',
children: [childSchema]
});
var Parent = mongoose.model('Parent', parentSchema);
Load Mongo with some data -
{
"firstName": "bobby",
"lastName": "edwards",
"blog": "this is blog 6",
"children": [
{
"firstName": "cat",
"lastName": "edwards",
"comment": "this is blog 6.1",
"children": [
{
"firstName": "dave",
"lastName": "edwards",
"comment": "this is blog 6.2",
"children": []
}]
}]
}
Get all blogs with nested comments from mongo and display correctly in angular - each blog or comment has a form attached
Return a single parent blog -
Create a parent blog -
UPDATED
I have managed to get one level down but for this i had to send the comment id as a param, modify the express route and pass the results of findbyid to results.comment.id({_id: req.params.comment_id}).
Node App
app.get('/parent/:post_id/:comment_id', postsController.single);
Node Controller
module.exports.single = function (req, res) {
console.log(req.params.post_id);
Parent.findById({ _id: req.params.post_id }, function (err, results) {
if (err) {
console.log(err);
}
var id = results.children.id({ _id: req.params.comment_id }); //function (err, commentresults) {
console.log('triggered 2');
console.log(id);
res.json([id]);
});
};
That being said this was for learning/testing and can't see how this would work if i needed to create/return/update a comment 10 levels down.
Looking at this from a different perspective, I'd just have two optional fields, topLevelCommentId and commentOwnerId, in your schema.
Anytime you insert a comment, then, as long as you appropriately set the topLevelCommentId as the top level parent, and commentOwnerId as the immediate parent, you should have no problem getting your comments nested appropriately.
Thanks for the feedback. I think I need to gain a better understanding of what is possible in MongoDB. Therefore I have decided to sit a couple of MongoDB University course. One on the Development Side and one on the Operation Side.
I'm writing an invitation application, and would like to email individual people unique URLs, e.g.
http://www.example.com/invitation.html?inviteID=-Jkbw6ycU7ZUOipmqlb5
The HTML app contains JavaScript that connects to a particular Firebase, looking up a node by the inviteID from the URL. Example:
https://my-firebase-123#firebaseio.com/-Jkbw6ycU7ZUOipmqlb5
Each top-level node looks roughly like
-Jkbw6ycU7ZUOipmqlb5: {
email: 'joe#gmail.com',
people: [
{name: 'Joe', accept: true},
{name: 'Jane', accept: false}
],
comments: 'Jane can't make it, but I'm looking forward to it!'
}
This already works great! But I'm having trouble understanding how to properly secure the data. I need the recipients to continue to be able to access those URLs without authentication - anyone who supplies a node ID can read and write to that node and its children - and yet I need to require auth to see the Firebase at its top level, so that invitees cannot see (or modify!) anyone else's responses without knowing other inviteIDs. How can I do this?
{
"rules": {
".read": ??
".write": ??
}
}
I expect both .read and .write will need a rule that means something like this:
"You requested a specific child node, not the top level node; otherwise you must be an authorized user (auth != null) to see the top level node."
The app is written in ReactJS and communicates with Firebase roughly like this:
componentWillMount: function() {
var dbAddress = 'my-firebase-123#firebaseio.com/';
this.firebaseRef = new Firebase(dbAddress + this.props.inviteId);
this.firebaseRef.on("value", function(dataSnapshot) {
this.setState(dataSnapshot.val());
}.bind(this));
},
onSend: function() {
this.firebaseRef.set(this.state);
},
I have been reading the various firebase docs trying to find a similar solution.
Assuming your firebase json structure is something like the following:
{ Invitations: {
-Jkbw6ycU7ZUOipmqlb5: {
email: 'joe#gmail.com',
people: [
{name: 'Joe', accept: true},
{name: 'Jane', accept: false}
],
comments: 'Jane can't make it, but I'm looking forward to it!'
}
-Jkbw6ycU7ZUOipmqlb6: {
... another invitation ...
}
-Jkbw6ycU7ZUOipmqlb7: {
... another invitation ...
}
}
I came up with the following security config which appears to do what you require:
{
"rules": {
".read": false,
".write": false,
"invitations": {
"$inviteid": {
".read": true,
".write": true
}
}
}
}
Actually the top level read/write false may be inferred because if I set the config as the following it seems to work in the same way:
{
"rules": {
"invitations": {
"$inviteid": {
".read": true,
".write": true
}
}
}
}
Now I cant seem to be able to browse the invitations as in if I try and mount at the following points I get permission denied (assuming your firebase address is https://my-firebase-123#firebaseio.com/:
this.firebaseRef = new Firebase('https://my-firebase-123#firebaseio.com/');
this.firebaseRef = new Firebase('https://my-firebase-123#firebaseio.com/invitations');
where as mounting at the following level lets me in:
this.firebaseRef = new Firebase('https://my-firebase-123#firebaseio.com/invitations/-Jkbw6ycU7ZUOipmqlb5');
Not sure if what I have done is actually achieving your requirements from a security perspective (i.e. is it actually secure?).
Would appreciate any feedback from the expert firebase community on this approach.