Nextauth with mysql persisting users.
I'm trying out this NextAuth thing to see if this is something I like. So far so good. There is one thing tho which is buggin me and that would be the user scheme. By default it returns a name, image and the last one I forgot.
I'd like to add more to this scheme and found some ways to do it by looking at google, however those I tried did not work.
One example I found is by extending the model which clearly makes sense...
The issue here is then me, I do not know what to change in the code below to make it work with my NextAuth credentials provider. As shown below, this doesnt work.
projectfolder -> models -> index.js
import User, { UserSchema } from "./User"
export default {
User: {
model: User,
schema: UserSchema
}
}
projectfolder -> models -> user.js
import Adapters from "next-auth/adapters"
// Extend the built-in models using class inheritance
export default class User extends Adapters.TypeORM.Models.User.model {
constructor(name, email, image, emailVerified, roles) {
super(name, email, image, emailVerified)
if (roles) { this.roles = roles}
}
}
export const UserSchema = {
name: "User",
target: User,
columns: {
...Adapters.TypeORM.Models.User.schema.columns,
roles: {
type: "varchar",
nullable: true
},
},
}
In my [...nextauth].js file I have my provider, in this provider i've added an profile() field with the extra fields. This did not solve the issue.
profile(profile) {
return {
name: profile.name,
email: profile.email,
role: profile.role
};
},
Please correct me if I am wrong but if I am using credentials, then I need to replace the "TypeORM" with something else, correct? How about the path for the files, are they correct?
This should clearly be quite easy but am I missing something or am I doing something wrong? I feel like there is a lack of documentation on extending the user model for mysql.
I've doubled checked that the role is being retrieved from the database and then added to the user variable shown here:
async authorize ....
const user = {
name: result.display_name,
role: result.role_name,
email: result.email
}
Although I can see the role being set in the variable with my console.log(), I still cannot access the role and that I suspect is because of the model. How would I resolve this? Thanks a lot in advance.
Any ideas?
----------------------- UPDATES ------------------------
Btw, here is my callback
callbacks: {
async signIn({ user, account, profile, email }) {
console.log("user", user);
return true;
},
},
and this is what it returns (shortened)
token: {
token: { name: 'Firstname Lastname', email: 'test#mail.com' },
user: {
name: 'Firstname Lastname',
role: 'administrator',
email: 'test#mail.com'
},
account: { type: 'credentials', provider: 'credentials' },
isNewUser: false,
iat: 1634193197,
exp: 1636785197
}
I'm new to TypeORM and I am facing the same problems as people here.
What I've done was create a separate Entity which I called users_info to store the other information and retrieve it after signing in.
It looks like this:
import { UserEntity } from './NextAuthEntities';
#Entity({ name: 'users_info' })
export class MemberEntity {
#PrimaryGeneratedColumn('increment')
id!: number;
#OneToOne(() => UserEntity)
#JoinColumn({
name: 'auth_id',
referencedColumnName: 'id',
})
auth_id!: UserEntity;
#Column({ type: 'varchar', nullable: true })
full_name!: string | null;
// etc
}
Then, I created a handshake API route to retrieve users_info if the user is signed-in.
When I added a new #Column on my custom UsersEntity, it threw me an error when I tried to login. It seems like TypeORMLegacyAdapter can't be extended or be different from the default UserEntity.
Hope it helps
Related
I'm having a problem within my application that is related to multiple tables/models arranged in a Many:Many relationship but also leverages polymorphic columns and is using the BelongsToManyAddAssociationMixin. I am unable to insert multiple records in a M:N relationship because I'm unable to tell Sequelize to change the unique fields to include roleableId
Essentially I have a User model, a Role model, a relationship table called UserRole and other models which are "roleable" (For the sake of this example, one model is a Facility)
So essentially my UserRole model looks like
export class UserRole extends Model {
public readonly id!: UUID
public RoleId!: UUID
public UserId!: UUID
public roleableType!: string
public roleableId!: string
public readonly User!: User
public readonly Role!: Role
public getUser!: BelongsToGetAssociationMixin<User>
public getRole!: BelongsToGetAssociationMixin<Role>
}
export const initializeUserRole = (sequelize: Sequelize) => {
UserRole.init(
{
RoleId: {
type: DataTypes.UUID,
allowNull: false,
validate: {
isUUID: 4,
},
field: 'role_id',
},
UserId: {
type: DataTypes.UUID,
allowNull: false,
validate: {
isUUID: 4,
},
field: 'user_id',
},
roleableId: {
type: DataTypes.UUID,
field: 'roleable_id',
validate: {
isUUID: 4,
},
},
roleableType: {
type: DataTypes.STRING,
field: 'roleable_type',
},
},
{
timestamps: false,
sequelize,
tableName: TableNames.USER_ROLES,
}
)
}
In my scenario, I would have a role named Sales Rep and it's primary key is a UUID
In theory, I would like to create a new record for the user to have the SalesRep role twice in my UserRole table, one with values for roleableId & roleableType and one where those values are null.
Using mixins, I'm able to add these roles with something like
const randomUser = await userFactory()
const salesRepRole = await roleFactory({ roleName: 'Sales Rep' })
await randomUser.addRole(salesRepRole)
^^^ This works properly
But if I try to do something like:
const randomUser = await userFactory()
const randomFacility = await facilityFactory()
const salesRepRole = await roleFactory({ roleName: 'Sales Rep' })
await randomUser.addRole(salesRepRole)
await randomuser.addRole(salesRepRole, { through: { roleableId: randomFacility.id, roleableType: 'facility' }})
What will happen is that instead of performing a new insert into my UserRole table, instead Sequelize will identify that there is already a record based upon User.id & Role.id and will perform an UPDATE instead, resulting in only one record being returned.
I'm not sure how to indicate to Sequelize that instead of putting a unique constraint on User.id & Role.id, I really need it to be User.id, Role.id & roleableId
My work around right now has been to simply just roll my own add<Model> function which will perform an INSERT properly (adding an index on my DB to check for unique instances of user_id,role_id,roleable that throw an error if this combination already exists) but this feels a bit dirty and I'd rather try to utilize the mixin as much as possible, especially if I also have to add a BelongsToManyAddAssociationsMixin version as well for addRoles
Does anyone have any ideas? I'm happy to elaborate more if that helps.
Thanks!
Am new to Graphql and actually following a tutorial. I am building a project in React Native and using AWS Amplify and Graphql for my backend. I needed a little change from the tutorial am following, I want users to be able to view user profile of other users in a their contact list just Instagram or Facebook.
In my schema.graphql I have the following code:
type User #model {
id: ID!
name: String!
imageUri: String
username: String!
email: String!
}
But I don't know the next code to write for user profile and the relationships for users to view other user user profiles.
I have been able to list contacts with the following code:
useEffect(() => {
const fetchUsers = async () => {
try {
const usersData = await API.graphql(
graphqlOperation(
listUsers
)
)
setUsers(usersData.data.listUsers.items);
} catch (e) {
console.log(e);
}
}
fetchUsers();
}, [])
Please I need guide on how to achieve viewing user profile when user is clicked on the contact list.
you have to add "auth" rule to your model
type User #model
#auth(
rules: [
#this is for logged-in user. cognito user is default for provider
# In order to prevent private users from creating, another rule must be set for creating
{ allow: private, operations: [read] }
# default provider is cognito
{ allow: private, provider: iam, operations: [read, create, update, delete] }
# Only Owner can update its own data
{ allow: owner, ownerField: "username", operations: [update] }
]
) {
id: ID!
name: String!
imageUri: String
username: String!
email: String!
}
In the above code, I defined two auth rules. One is for Cognito user, he can only read, another one for the "iam" user who has more privileges.
I am attempting to create Gatsby pages programmatically using the Gatsby API createPages and data from Firebase. I've set up everything successfully up to the point where Firebase data is accessible via GraphQL and now I want to query specifict data for each of the new pages that were created using id (which are in string format). However, when I create the template component and try to query the data i get this error:
Variable "$clientId" of type "String!" used in position expecting type "StringQueryOperatorInput".
I have looked everywhere for a reference of this StringQueryOperatorInput and can't find any info on it. Google and graphql docs don't seem to mention the term and this is my first time seeing it. After troubleshooting for an hour I got a different error:
If you're e.g. filtering for specific nodes make sure that you choose the correct field (that has the same type "String!") or adjust the context variable to the type "StringQueryOperatorInput".
File: src/templates/Homeowner/Homeowner.js:24:9
However, I still don't know what a StringQueryOperatorInput is or how to fix this.
Below is my code for this component and my gatsby-node.js, and my gatsby-config.js where i use a plugin to source the Firebase data.
I could really use some help on this, I can't seem to find any reference of this StringQueryOperatorInput.
Everything else works fine, I just can't get this query on the Homeowner.js template to work.
gatsby-node.js
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
query {
allClients {
nodes {
firstName
lastName
id
}
}
}
`);
console.log(JSON.stringify(result, null, 4));
result.data.allClients.nodes.forEach(node => {
const slug = `/client/${node.id}`;
createPage({
path: slug,
component: require.resolve(`./src/templates/Homeowner/Homeowner.js`),
context: { clientId: node.id },
});
});
};
src/templates/Homeowner/Homeowner.js
import React from 'react';
import { graphql } from 'gatsby';
import { withFirebase } from '../../components/Firebase';
import { withStyles } from '#material-ui/core/styles';
import Layout from '../../components/layout';
const Homeowner = ({ data }) => {
console.log(data.clients, 'data');
return (
<>
<Layout>
<h1>Home Owner Component</h1>
{/* <h3>{client.firstName}</h3>
<h3>{client.lastName}</h3>
<h3>{client.email}</h3> */}
</Layout>
</>
);
};
export default Homeowner;
export const query = graphql`
query($clientId: String!) {
clients(id: $clientId) {
firstName
lastName
email
}
}
`;
gatsby-config.js
require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`,
});
module.exports = {
siteMetadata: {
title: `SiteTitle`,
siteUrl: `https://www.mysitwe.com`,
description: `YourSite`,
},
plugins: [
`gatsby-plugin-react-helmet`,
`gatsby-plugin-sitemap`,
`gatsby-plugin-styled-components`,
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
{
resolve: `gatsby-source-firebase`,
options: {
credential: require('./firebase-key.json'),
databaseURL: 'https://firebaseurl/',
types: [
{
type: 'Clients',
path: 'clients',
},
{
type: 'Users',
path: 'users',
},
],
},
},
{
resolve: `gatsby-plugin-prefetch-google-fonts`,
options: {
fonts: [
{
family: `Nunito Sans`,
variants: [`400`, `600`, `800`],
},
{
family: `Montserrat`,
variants: [`300`, `400`, `400i`, `500`, `600`],
},
{
family: `Spectral`,
variants: [`400`, `600`, `800`],
},
{
family: `Karla`,
variants: [`400`, `700`],
},
],
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-plugin-offline`,
],
};
THank you in advance if anyone can help me out.
Actually literally right after I posted this question I found the solution. I needed to set up my query like so:
export const query = graphql`
query($clientId: String!) {
clients(id: { eq: $clientId }) {
firstName
lastName
email
}
}
`;
I assume that leaving out the {eq: $clientId} throws that StringQuery error on the GraphQL side. I still do not know what a StringQueryOperatorInput is, however, I have successfully generated the pages with the data from firebase.
StringQueryOperatorInput is the type of the id argument of the clients field. GraphQL includes scalar types like String, ID or Int as well as types that describe more complex data structures like arrays or objects. In this case, StringQueryOperatorInput is an input object type -- it describes objects that can be used as inputs like argument values or variables.
When filtering fields, Gatsby uses an input object like this to enable using a variety of comparison operators to filter the exposed data -- in addition to eq (equals), you can use other operators like ne, regex, in, gt, etc. You can see the full list here. Because not all inputs apply to all scalars (regex makes sense for a String field but lte does not), there's a different input type for each scalar (IntQueryOperatorInput, BooleanQueryOperatorInput, etc.)
Gatsby exposes a GraphiQL endpoint in development. Writing queries using GraphiQL allows you to utilize autocomplete and syntax highlighting so that you can avoid unexpected syntax errors like this. You can also use the "Docs" button to search and browse the entire schema.
I'm having some issues updating an array in the resolver. I'm building with typescript.
Description
I have in the datamodel.graphql for Prisma:
type Service #model {
id: ID! #unique
title: String
content: String
createdAt: DateTime!
updatedAt: DateTime!
comments: [Comment!]! // Line to be seen here
author: User!
offer: Offer
isPublished: Boolean! #default(value: "false")
type: [ServiceType!]!
}
type Comment #model {
id: ID! #unique
author: User! #relation(name: "WRITER")
service: Service!
message: String!
}
The Prisma is connected to the GraphQl server and in this one, I defined the mutation :
commentService(id: String!, comment: String!): Service!
So comes the time for implementing the resolver for the given mutation and I'm doing this :
async commentService(parent, {id, comment}, ctx: Context, info) {
const userId = getUserId(ctx);
const service = await ctx.db.query.service({
where: {id}
});
if (!service) {
throw new Error(`Service not found or you're not the author`)
}
const userComment = await ctx.db.mutation.createComment({
data: {
message: comment,
service: {
connect: {id}
},
author: {
connect: {id:userId}
},
}
});
return ctx.db.mutation.updateService({
where: {id},
data: {
comments: {
connect: {id: userComment.id}
}
}
})
}
The problem :
The only thing I'm receiving when querying the playground is null instead of the comment I've given.
Thanks for reading till so far.
Can you please share code where you expose mutation resolvers? You might get null in response in case you forgot to include commentService resolver in mutation resolver object.
Apart from this, I see one more issue in the code. Since you have relation between Service and Comment, you can use single mutation to create comment and add it in service. You don't need to write two separate mutations in order to achieve that. Your resolver can be changed to be as simple as below:
async commentService(parent, {id, comment}, ctx: Context, info) {
const userId = getUserId(ctx);
return ctx.db.mutation.updateService({
where: {id},
data: {
comments: {
create: {
message: comment,
author: {
connect: {id:userId}
}
}
}
}
})
}
Note that I also removed query to check if service exists before performing update. Reason being, updateService binding call will throw error in case it does not exist, we don't need to explicitly check for that.
If I understood the question correctly, you are calling this commentService mutation and you get null as a result? Following your logic, you should get whatever ctx.db.mutation.updateService resolves with, right? If you expect that to indeed be a Service object, then the only reason why you might not be getting it back is a missing await. You probably needed to write return await ctx.db.mutation.updateService({ ....
I am getting started with Sequelize. I am following the documentation they are providing on their website :http://docs.sequelizejs.com/manual/installation/getting-started.html
const Sequelize = require('sequelize');
const sequelize = new Sequelize('haha', 'postgres', 'postgres', {
host: 'localhost',
dialect: 'postgres',
operatorsAliases: false,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
// SQLite only
storage: 'path/to/database.sqlite'
});
sequelize
.authenticate()
.then(() => {
console.log('Connection has been established successfully.');
})
.catch(err => {
console.error('Unable to connect to the database:', err);
});
const User = sequelize.define('user', {
firstName: {
type: Sequelize.STRING
},
lastName: {
type: Sequelize.STRING
}
});
// force: true will drop the table if it already exists
User.sync({force: true}).then(() => {
// Table created
return User.create({
firstName: 'John',
lastName: 'Hancock'
});
});
Up until here, everything works perfectly. And the table "user" is correctly built and populated. (Although I do not understand Sequelize appends an "s" automatically to "user", any explanation.)
However when I add the following portion of code:
User.findAll().then(users => {
console.log(users)
})
I get this error :
Unhandled rejection SequelizeDatabaseError: relation "users" does not
exist
So my questions are:
Why does Sequelize add an "s" to user. (I know it makes sense but shouldn't the developer decide that)
What is causing that error? I followed the documentation but it still didn't work?
When you are defining your model you can add configurations, in this case the option that you must add is freezeTableName prevents the names from being plural.
const User = sequelize.define('user', {
firstName: {
type: Sequelize.STRING
},
lastName: {
type: Sequelize.STRING
}
}, {
// disable the modification of table names; By default, sequelize will automatically
// transform all passed model names (first parameter of define) into plural.
// if you don't want that, set the following
freezeTableName: true,
});
There is another interesting way you can avoid this. But you need to really focus on this way of implementation.
const User = sequelize.define("user", {
firstname: {
type: Sequelize.STRING
},
lastname: {
type: Sequelize.STRING
}
});
you intentionally put user here and use users in other places of coding(Assume sequelize will automatically transform all passed model names (first parameter of define) into plural) . This way of coding will simplify your code.
This problem occurs because creating a table is an asynchronous function. The problem is, the findAll() function can get executed while the table has not been created.
to solve this, you can use:
(async ()=>{
await User.sync({force: true});
// Table created
const users=await User.findAll();
console.log(users);
})();
The problem, in my case, was that the table users was not created. You can create the table manually with CREATE TABLE IF NOT EXISTS (SQL) or add the tableName = "users" in the options object:
export const User = db.define('user',
{
id: {
type: DataTypes.UUIDV4,
autoIncrement: true,
primaryKey: true,
},
name: {
type: new DataTypes.STRING(128),
allowNull: false,
},
email: {
type: new DataTypes.STRING(128),
allowNull: true,
},
password: {
type: new DataTypes.STRING(128),
allowNull: true,
},
},
{
freezeTableName: true,
tableName: "users"
}
);
Run that code twice.
Before running the second time, comment out the following code,
// force: true will drop the table if it already exists
User.sync({force: true}).then(() => {
// Table created
return User.create({
firstName: 'John',
lastName: 'Hancock'
});
});
Maybe answer is not entirely connected with you question but I want to describe my experience with this error
Error: relation "users" does not exist.
It appears Sequelize make migrations based on migrations file names and it alphabetical order. My problem was my files naming was not sorted in order to create proper connections.
If you face with this problem make sure yours migration files are fired in proper (in alphabetically) order.
The proper order is to first migrate table without connections (eg. table_A) and then tables with connections to table_A.
As I said this may not be answer for your particular order but I want to share my experiences because I didn't find this information on the internet when I was looking for this error.
Simply append tableName: "Users" to your model configuration.
The easiest way I found to solve, is to explicitly set the tableName on the model. As others have mentioned, sequelize defaults to the plural form of a model as the table name. For instance User, becomes Users.
When you query, sequelize looks after a table with the same name as your model User. By defining the tableName in the model, sequelize should search the correct table. Append tableName: "Users" to your model configuration i.e:
User.init(
{
email: DataTypes.STRING,
password: DataTypes.STRING,
role: DataTypes.INTEGER,
},
{
sequelize,
modelName: 'User',
tableName: 'Users',
}
);
If you want Sequelize to use a singular word ('info') for a model and that same singular word for the table ('info'), you can name the model 'info' and also add tablename: 'info' to the definition of your model.
This is a way to control Sequelize's default behavior of pluralizing model names, or not, on a table-by-table basis.
info.js
module.exports = (sequelize, DataTypes) => {
const info = sequelize.define('info', {
firstname: DataTypes.STRING,
email: DataTypes.STRING,
phone: DataTypes.STRING,
}, {
tableName: 'info'
});
return info;
};