I want to create a function to update a specific document in MongoDB by it's id but only update the fields if the new provided value is not the Go default.
This is the document struct which I store in MongoDB:
type User struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Username string `json:"username" bson:"username"`
FirstName string `json:"firstName" bson:"first_name"`
LastName string `json:"lastName,omitempty" bson:"last_name,omitempty"`
Email string `json:"email" bson:"email"`
Password string `json:"password,omitempty" bson:"password"`
PhoneNumber string `json:"phoneNumber,omitempty" bson:"phone_number,omitempty"`
Picture string `json:"picture,omitempty" bson:"picture,omitempty"`
Role Role `json:"role" bson:"role"`
}
My update function gets the id of the user document to update and a user struct with only the fields provided which should get updated. So if only the username should get updated all other fields in the provided user struct will have their default value.
I now need to first check if for example the new username is not empty and only then include it on the new update document.
This is how I would solve it in javsacript. Is there a similar solution for Go?
{
...(username && { username: username }),
...(email && { email: email }),
...(firstname && { firstname: firstname }),
...(lastname && { lastname: lastname }),
...(phone && { phone: phone }),
...(picture && { picture: picture }),
},
This is my Update function:
func (us *userQuery) Update(userId string, u datastruct.User) (*datastruct.User, error) {
userCollection := DB.Collection(datastruct.UserCollectionName)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
_id, err := primitive.ObjectIDFromHex(userId)
if err != nil {
return nil, err
}
update := bson.D{{
Key: "$set",
Value: bson.D{
{Key: "username", Value: u.Username}, // only include a field if provided value is not the default
{Key: "firstName", Value: u.FirstName},
{Key: "lastName", Value: u.LastName},
{Key: "email", Value: u.Email},
{Key: "password", Value: u.Password},
{Key: "phoneNumber", Value: u.PhoneNumber},
{Key: "picture", Value: u.Picture},
},
}}
var result datastruct.User
res, err := userCollection.UpdateByID(
ctx,
_id,
update,
)
if err != nil {
return nil, err
}
return &result, nil
}
You have to build the update clause dynamically:
value:=bson.M{}
if len(u.UserName)!=0 {
value["username"]=u.UserName
}
if len(u.FirstName)!=0 {
value["firstName"]=u.FirstName
}
...
if len(value)>0 { // Is there anything to update?
res, err := userCollection.UpdateByID(
ctx,
_id,
bson.M{"$set":value})
}
Related
I have an application that has only the POST method, and every time I make a request in that application, it saves some data for me in my postgres database..
locally my application works properly, but when I deploy and try to test it through the azure function app it returns a 500 error and says that it was not possible to store my information in the database..
here are some code snippets
go code:
func (h handler) sendgridWeb(c *gin.Context) {
data, _ := io.ReadAll(c.Request.Body)
var ListaEventoEmail []Email
if err := json.Unmarshal(data, &ListaEventoEmail); err != nil {
log.Fatal("Error when unrealizing content contained in Body")
}
for _, email := range ListaEventoEmail {
email.SgMessageId = email.SgMessageId[:strings.IndexByte(email.SgMessageId, '.')]
//-- PROCESSED --//
if email.Event == "processed" {
inserting := `insert into email (email_destinatario, enviado, id_sendgrid) values ($1, $2, $3)`
_, err := h.DB.Exec(inserting, email.Email, true, email.SgMessageId)
if err != nil {
log.Fatal("Error inserting data into email table")
}
}
//-- DELIVERED --//
if email.Event == "delivered" {
_, err := h.DB.Exec(`UPDATE email SET recebido=true WHERE id_sendgrid=$1`, email.SgMessageId)
if err != nil {
log.Fatal("Error when uploading email with DELIVERED event")
}
//-- OPEN --//
} else if email.Event == "open" {
_, err := h.DB.Exec(`UPDATE email SET recebido=true, aberto=true WHERE id_sendgrid=$1`, email.SgMessageId)
if err != nil {
log.Fatal("Error when uploading email with OPEN event")
}
} else if email.Event == "click" {
_, err := h.DB.Exec(`UPDATE email SET click = click + 1 WHERE id_sendgrid=$1`, email.SgMessageId)
if err != nil {
log.Fatal("Error when uploading email with CLICK event")
}
}
my connection string is stored in a toml file..
connection string:
# filename: config.toml
[postgres]
host = "132.168.0.22"
port = 5432
user = "postgres"
password = "ivaneteJC"
dbname = "webhook"
you can see that, in the HOST field I put my IP (fictitious ip)..
in my logic, when deploying to azure, my connection string should point to my machine, so I used the ip, thus performing the necessary operations on my database, but this does not happen .. when making a POST request by the postman (using the endpoint that o was provided when deploying to the azure function app, I get a 500 error and in my logs I get the message "Error inserting data into the email table" which is an error described in my go app, I believe I made a mistake somewhere that has a connection to the database, can someone help me?
function.json:
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
host.json:
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[3.*, 4.0.0)"
},
"customHandler": {
"description": {
"defaultExecutablePath": "main.exe",
"workingDirectory": "",
"arguments": []
},
"enableForwardingHttpRequest": true
}
}
local.settings.json:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "custom"
}
}
I did as mentioned in a comment, I treated the error in an original way .. and the following error appeared:
"connectex: An attempt was made to access a socket in a way prohibited by its access permissions."
db:
type handler struct {
DB *sql.DB
}
type PostgreConfig struct {
Host string
Port int
User string
Password string
Dbname string
}
type MyConfig struct {
Postgres *PostgreConfig
}
func Init() *sql.DB {
doc, err := os.ReadFile("config.toml")
if err != nil {
panic(err)
}
var cfg MyConfig
err = toml.Unmarshal(doc, &cfg)
if err != nil {
panic(err)
}
stringConn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", cfg.Postgres.Host, cfg.Postgres.Port, cfg.Postgres.User, cfg.Postgres.Password, cfg.Postgres.Dbname)
db, err := sql.Open("postgres", stringConn)
if err != nil {
log.Fatal("Erro ao realizar conexão")
} else {
fmt.Println("Conectado")
}
fmt.Println(stringConn)
db.Ping()
return db
}
func OpenDB(DB *sql.DB) handler {
return handler{DB}
}
I am trying to create an API with MongoDB, Express Js, Node and GraphQl. I have a collection called characters, with the following schema:
const CharacterSchema = Schema({
page:{
type: Number,
required: true
},
data:{
type: Array,
required: true
}
});
I have 25 objects in my database with the above schema. I have a query to query the characters, passing the page number by parameter:
type Character {
_id: ID
name: String!
status: String!
species: String!
type: String!
gender: String!
origin: String!
image: String!
episode: [String]
location: String!
created: String!
}
type Page {
page: Int!
data: [Character]!
}
type Query {
characters(page: Int!): Page!
}
And this is its resolver:
export const resolvers = {
Query: {
characters: async (_, args) => {
let data = await Character.findOne({ page: args.page });
return data;
},
},
};
This is the query Im using to fetch the data:
query($page: Int!) {
characters(page: $page) {
page
data {
name
status
species
type
gender
origin
image
episode
location
created
}
}
}
Executing the query by passing the page number, it returns perfectly the information I ask for.
Now I want to get only one character by its ID. I created a query and a type to fetch only one character by its id:
type CharacterById {
result: Character
}
type Query {
characters(page: Int!): Page!,
character(id: ID): CharacterById
}
This is its resolver:
export const resolvers = {
Query: {
//this works perfectly
characters: async (_, args) => {
let data = await Character.findOne({ page: args.page });
return data;
},
//returns obj but show me null
character: async (_, args) => {
//first method returns the object perfectly
let data = await Character.aggregate([
{ $unwind: "$data" },
{ $match: { "data._id": args.id } },
]);
return data[0].data // returns object
//second method returns the object perfectly
let data = await Character.findOne({"data._id": args.id})
let character = data.data.find(item => item._id === args.id)
return character // returns object
},
},
};
I explain the above: The query “character” is the resolver that I created to get from the database the character with the id passed by parameter.
I try it with two methods. Both of them return me perfectly the object with the id passed by parameter, but when I try to use the query:
query($characterId: ID!) {
character(id: $characterId) {
result {
name
status
species
type
gender
origin
image
episode
location
created
}
}
}
It returns me a null, when it should return me the object:
{
"data": {
"character": null
}
}
why doesn't it bring me the object?
please help me I am very stressed and frustrated that this is not working for me :(
Having issue with inserting an employee and a user that have a relationship (only in the db ;))
The principle: A user can create multiple employees and then give them access by adding them as a user. An employee can have a user account. A user can be an employee.
Only one user account per employee. Also, by putting the password in a different table I hope to mitigate some accidental leaks where the password might be sent to the client, etc. [I still need to encrypt email and phone number in the db]
My Prisma model:
model User {
id String #id #default(dbgenerated("gen_random_uuid()")) #db.Uuid
username String #unique
password_hash Bytes
employee Employee? #relation("EmployeeUserInfo")
employeeId String #unique #db.Uuid
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
employeesAdded Employee[] #relation("EmployeeAddedByUser")
}
model Employee {
id String #id #default(dbgenerated("gen_random_uuid()")) #db.Uuid
first_name String
last_name String
phone_number String
user User? #relation("EmployeeUserInfo", fields: [userId], references: [id])
userId String #unique #db.Uuid
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
addedBy User? #relation("EmployeeAddedByUser", fields: [addedById], references: [id])
addedById String #db.Uuid
}
My Prisma client code:
// let user = await prisma.user.create({
// data: {
// username: 'system',
// password_hash: Buffer.from(hash),
// employee: {
// create: {
// first_name: 'system',
// last_name: 'system',
// phone_number: '5555555555'
// }
// },
// },
// });
let user = await prisma.employee.create({
data: {
first_name: 'system',
last_name: 'system',
phone_number: '5555555555',
user: {
create: {
username: 'system',
password_hash: Buffer.from(hash),
}
},
},
select: {
user: true
}
});
The error I receive is:
Invalid `prisma.employee.create()` invocation:
{
data: {
first_name: 'system',
last_name: 'system',
phone_number: '5555555555',
user: {
create: {
username: 'system',
password_hash: Buffer(3),
+ employeeId: String,
? id?: String,
? createdAt?: DateTime,
? updatedAt?: DateTime,
? employeesAdded?: {
? create?: EmployeeCreateWithoutAddedByInput | EmployeeCreateWithoutAddedByInput | EmployeeUncheckedCreateWithoutAddedByInput | EmployeeUncheckedCreateWithoutAddedByInput,
? connectOrCreate?: EmployeeCreateOrConnectWithoutAddedByInput | EmployeeCreateOrConnectWithoutAddedByInput,
? createMany?: EmployeeCreateManyAddedByInputEnvelope,
? connect?: EmployeeWhereUniqueInput | EmployeeWhereUniqueInput
? }
}
}
},
select: {
user: true
}
}
Argument employeeId for data.user.create.employeeId is missing.
Note: Lines with + are required, lines with ? are optional.
I tried in both directions: User creates employee and Employee creates user.
With the commented code, I get a similar message Argument employeeId for data.employeeId is missing..
I guess I can't make this work because they both reference each other (even if they are optional)? If so, what would make the most sense from a security/db perspective? I saw a suggestion about using select to return the id but unless I'm not using it the right way, it doesn't work.
Using Nexus-Prisma in the background, in case that helps.
I encountered a problem while trying to seed the user table.
I have a one-to-one relationship between the User and UserSettings model in it.
async function generateUsers() {
const users = [];
for (let i = 0; i < randomUsersCount; i++) {
users[i] = {
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
email: faker.internet.email(),
password: await hashPassword('testtest'),
phone: faker.phone.phoneNumber('###-###-###'),
role: 'USER',
is_blocked: false,
user_settings : {
create: {
language: 'PL',
color: faker.internet.color(),
}
}
};
}
await prisma.user.createMany({
data: users,
});
}
Error message:
Unknown arg `user_settings` in data.0.user_settings for type UserCreateManyInput. Available args:
type UserCreateManyInput {
id?: Int
first_name: String
last_name: String
email: String
phone: String
password: String
role: UserRoles
is_blocked?: Boolean
created_at?: DateTime
updated_at?: DateTime
}
but in my model the field exists:
model User {
id Int #id #default(autoincrement())
first_name String
last_name String
email String #unique
phone String
password String
role UserRoles
is_blocked Boolean #default(false)
created_at DateTime #default(now())
updated_at DateTime #updatedAt
user_settings UserSettings?
}
model UserSettings {
id Int #id #default(autoincrement())
language Languages
color String
user_id Int
user User #relation(fields: [user_id], references: [id])
}
When I turn on Prisma Studio, I can see the column present in the table.
Where did I go wrong?
I have run npx prisma generate and npx prisma migrate and even npx prisma migrate reset many times, without success.
You are trying to use nest createMany which isn't supported.
This is from the documentation:
You cannot create or connect relations - you cannot nest create,
createMany, connect, connectOrCreate inside a top-level createMany
As an alternative I would suggest to create users inside the loop while you are creating data.
async function generateUsers() {
const users = [];
for (let i = 0; i < randomUsersCount; i++) {
users[i] = {
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
email: faker.internet.email(),
password: await hashPassword('testtest'),
phone: faker.phone.phoneNumber('###-###-###'),
role: 'USER',
is_blocked: false,
user_settings : {
create: {
language: 'PL',
color: faker.internet.color(),
}
}
};
await prisma.user.create({
data: users[i],
});
}
}
I want to add a mongo user to the mongodb. I tried the following:
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"log"
"os"
"time"
)
type User struct {
User string `json:"user"`
Db string `json:"db"`
Roles []Role`json:"roles"`
PasswordDigestor string `json:"passwordDigestor"`
Pwd string `json:"pwd"`
}
type Role struct {
Role string `json:"role"`
Db string `json:"db"`
}
func CreateUser() {
client, err := mongo.NewClient(options.Client().ApplyURI(fmt.Sprintf("mongodb://%s:%s#%s/test?authSource=%s&replicaSet=%s",
mongoConf.user,mongoConf.password,mongoConf.host,mongoConf.authDB,mongoConf.replicaset)))
if err != nil {
log.Fatal(err)
}
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
err = client.Connect(ctx)
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(ctx)
//pass := password(8)
pass := "Test123!"
if _, err = client.Database("admin").Collection("system.users").InsertOne(context.Background(),User{
User: "test",
Db: "admin",
Roles: []Role{
{Role: "readWriteAnyDatabase",
Db: "admin",
},
},
PasswordDigestor: "server",
Pwd: pass,
}); err != nil {
panic(err)
}
}
But that is not working. With the shell it is not a problem with db.createUser(). But how I'm supposed to do this with go? I was able to list all users from the system.users table. When I insert a user like this into the database its working without problems, but it is not possible to log in with the created user.
EDIT:
runnint db.createUser:
function (userObj, writeConcern) {
var name = userObj["user"];
var cmdObj = {createUser: name};
cmdObj = Object.extend(cmdObj, userObj);
delete cmdObj["user"];
this._modifyCommandToDigestPasswordIfNecessary(cmdObj, name);
cmdObj["writeConcern"] = writeConcern ? writeConcern : _defaultWriteConcern;
var res = this.runCommand(cmdObj);
if (res.ok) {
print("Successfully added user: " + getUserObjString(userObj));
return;
}
if (res.errmsg == "no such cmd: createUser") {
throw Error("'createUser' command not found. This is most likely because you are " +
"talking to an old (pre v2.6) MongoDB server");
}
if (res.errmsg == "timeout") {
throw Error("timed out while waiting for user authentication to replicate - " +
"database will not be fully secured until replication finishes");
}
throw _getErrorWithCode(res, "couldn't add user: " + res.errmsg);
}
Thanks to #Joe I could solve the problem running the following command:
r := client.Database(dbName).RunCommand(context.Background(),bson.D{{"createUser", userName},
{"pwd", pass}, {"roles", []bson.M{{"role": roleName,"db":roldeDB}}}})
if r.Err() != nil {
panic(r.Err())
}