In this example we are going to keep multiple documents in another document. This is called "embedded document model" in MongoDB. Our example uses "tokens" collection. Each token document will contain zero or many "permission" documents in it.


Database content


Preparation


db.createCollection("tokens")
db.tokens.createIndex({"uuid":1},{unique:true,name:"UQ_uuid"})

Tokens data


[
{
"_id": {
"$oid": "6050986944799a96c0f7b1f4"
},
"uuid": "7d5e2a9f-b6b9-4356-af3c-be772b3cf821",
"type": "access token",
"permissions": [
{
"uuid": "f2222ef5-7f9b-46fa-a7e6-c88b4126e9fe",
"type": "accounts",
"actions": [
"read",
"write"
]
},
{
"uuid": "2cffcc36-0efc-4d09-bb0a-a36f2b6433be",
"type": "balances",
"actions": [
"read"
]
}
]
},
{
"_id": {
"$oid": "605098e921410aa61d2be92b"
},
"uuid": "38a934d7-73cf-4874-8af1-874d4c03335f",
"type": "refresh token",
"permissions": null
}
]

Storage


Models


package storage

import "context"

type TokenStorer interface {
Insert(ctx context.Context, token Token) error
Find(ctx context.Context, uuid string) (Token, error)
}

type Token struct {
ID string `bson:"_id,omitempty"`
UUID string `bson:"uuid"`
Type string `bson:"type"`
Permissions []Permission `bson:"permissions"`
}

type Permission struct {
UUID string `bson:"uuid"`
Type string `bson:"type"`
Actions []string `bson:"actions"`
}

Storer


package mongodb

import (
"context"
"log"
"time"

"github.com/you/mongo/internal/pkg/domain"
"github.com/you/mongo/internal/pkg/storage"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)

var _ storage.TokenStorer = TokenStorage{}

type TokenStorage struct {
Database *mongo.Database
Timeout time.Duration
}

func (t TokenStorage) Insert(ctx context.Context, token storage.Token) error {
ctx, cancel := context.WithTimeout(ctx, t.Timeout)
defer cancel()

if _, err := t.Database.Collection("tokens").InsertOne(ctx, token); err != nil {
log.Println(err)

if er, ok := err.(mongo.WriteException); ok && er.WriteErrors[0].Code == 11000 {
return domain.ErrConflict
}

return domain.ErrInternal
}

return nil
}

func (t TokenStorage) Find(ctx context.Context, uuid string) (storage.Token, error) {
ctx, cancel := context.WithTimeout(ctx, t.Timeout)
defer cancel()

var tok storage.Token

qry := bson.M{"uuid": uuid}

err := t.Database.Collection("tokens").FindOne(ctx, qry).Decode(&tok)
if err != nil {
log.Println(err)

if err == mongo.ErrNoDocuments {
return storage.Token{}, domain.ErrNotFound
}

return storage.Token{}, domain.ErrInternal
}

return tok, nil
}

Test


Insert


storer := TokenStorage{Database: database, Timeout: time.Second * 5}

if err := storer.Insert(context.Background(), Token{
UUID: uuid.New().String(),
Type: "access token",
Permissions: []Permission{
{
UUID: uuid.New().String(),
Type: "accounts",
Actions: []string{"read", "write"},
},
{
UUID: uuid.New().String(),
Type: "balances",
Actions: []string{"read"},
},
},
}); err != nil {
log.Fatalln(err)
}

Find


storer := TokenStorage{Database: database, Timeout: time.Second * 5}

token, err := storer.Find(context.Background(), "7d5e2a9f-b6b9-4356-af3c-be772b3cf821")
if err != nil {
log.Fatalln(err)
}

data, _ := json.MarshalIndent(token, "", " ")
log.Println(string(data))

{
"ID": "6050986944799a96c0f7b1f4",
"UUID": "7d5e2a9f-b6b9-4356-af3c-be772b3cf821",
"Type": "access token",
"Permissions": [
{
"UUID": "f2222ef5-7f9b-46fa-a7e6-c88b4126e9fe",
"Type": "accounts",
"Actions": [
"read",
"write"
]
},
{
"UUID": "2cffcc36-0efc-4d09-bb0a-a36f2b6433be",
"Type": "balances",
"Actions": [
"read"
]
}
]
}