2023-06-12 14:19:10 +00:00
|
|
|
package repository
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-07-18 22:32:20 +00:00
|
|
|
"mime/multipart"
|
2023-09-15 00:39:38 +00:00
|
|
|
"strings"
|
2023-07-18 22:32:20 +00:00
|
|
|
"time"
|
|
|
|
|
2023-06-12 14:19:10 +00:00
|
|
|
"github.com/minio/minio-go/v7"
|
|
|
|
"github.com/minio/minio-go/v7/pkg/policy"
|
2023-07-03 16:55:26 +00:00
|
|
|
"github.com/minio/minio-go/v7/pkg/set"
|
2023-06-12 14:19:10 +00:00
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
|
|
"go.uber.org/zap"
|
2023-07-03 11:40:20 +00:00
|
|
|
"penahub.gitlab.yandexcloud.net/backend/verification/internal/models"
|
2023-06-12 14:19:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type VerificationRepository struct {
|
|
|
|
logger *zap.Logger
|
|
|
|
mongo *mongo.Collection
|
|
|
|
s3 *minio.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
2023-09-15 00:39:38 +00:00
|
|
|
VerificationEndpointURL = "https://hub.pena.digital"
|
|
|
|
VerificationBucket = "verification1"
|
|
|
|
VerificationCollection = "verification"
|
2023-06-12 14:19:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func NewVerificationRepository(logger *zap.Logger, mongoDb *mongo.Database, s3 *minio.Client) *VerificationRepository {
|
|
|
|
return &VerificationRepository{
|
|
|
|
logger: logger,
|
|
|
|
mongo: mongoDb.Collection(VerificationCollection),
|
|
|
|
s3: s3,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *VerificationRepository) Init(ctx context.Context) error {
|
|
|
|
ok, err := r.s3.BucketExists(ctx, VerificationBucket)
|
|
|
|
if r.err(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ok {
|
2023-07-03 16:55:26 +00:00
|
|
|
err = r.s3.MakeBucket(ctx, VerificationBucket, minio.MakeBucketOptions{ObjectLocking: false})
|
2023-06-12 14:19:10 +00:00
|
|
|
if r.err(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-07-03 16:55:26 +00:00
|
|
|
policyConsoleStatement := policy.Statement{
|
|
|
|
Actions: set.CreateStringSet("*"),
|
|
|
|
Conditions: policy.ConditionMap{
|
|
|
|
"StringLike": policy.ConditionKeyMap{
|
|
|
|
"aws:referer": set.CreateStringSet(fmt.Sprintf("https://console.cloud.yandex.*/folders/*/storage/buckets/%s*", VerificationBucket)),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Effect: "Allow",
|
|
|
|
Principal: policy.User{AWS: set.CreateStringSet("*")},
|
|
|
|
Resources: set.CreateStringSet(fmt.Sprintf("arn:aws:s3:::%s/*", VerificationBucket),
|
|
|
|
fmt.Sprintf("arn:aws:s3:::%s", VerificationBucket)),
|
|
|
|
Sid: "console-statement",
|
|
|
|
}
|
|
|
|
|
|
|
|
policyServiceAccount := policy.Statement{
|
|
|
|
Actions: set.CreateStringSet("*"),
|
|
|
|
Conditions: nil,
|
|
|
|
Effect: "Allow",
|
|
|
|
Principal: policy.User{CanonicalUser: set.CreateStringSet("ajelmc4tjbct675tjdh9")},
|
|
|
|
Resources: set.CreateStringSet(fmt.Sprintf("arn:aws:s3:::%s/*", VerificationBucket),
|
|
|
|
fmt.Sprintf("arn:aws:s3:::%s", VerificationBucket)),
|
|
|
|
Sid: "service-account-statement",
|
|
|
|
}
|
2023-06-12 14:19:10 +00:00
|
|
|
|
2023-07-03 16:55:26 +00:00
|
|
|
policySharingBucket := policy.Statement{
|
2023-07-03 16:58:39 +00:00
|
|
|
Actions: set.CreateStringSet("s3:GetObject"),
|
|
|
|
Conditions: nil,
|
|
|
|
Effect: "Allow",
|
|
|
|
Principal: policy.User{AWS: set.CreateStringSet("*")},
|
2023-07-03 16:55:26 +00:00
|
|
|
Resources: set.CreateStringSet(fmt.Sprintf("arn:aws:s3:::%s/*", VerificationBucket),
|
|
|
|
fmt.Sprintf("arn:aws:s3:::%s", VerificationBucket)),
|
|
|
|
Sid: "sharing-bucket",
|
|
|
|
}
|
|
|
|
|
|
|
|
p := policy.BucketAccessPolicy{Version: "2012-10-17", Statements: []policy.Statement{
|
|
|
|
policyConsoleStatement,
|
|
|
|
policyServiceAccount,
|
|
|
|
policySharingBucket,
|
|
|
|
}}
|
2023-06-12 14:19:10 +00:00
|
|
|
|
|
|
|
outPolicy, err := json.Marshal(&p)
|
|
|
|
if r.err(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = r.s3.SetBucketPolicy(ctx, VerificationBucket, string(outPolicy))
|
|
|
|
if r.err(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *VerificationRepository) Insert(
|
|
|
|
ctx context.Context,
|
|
|
|
userID string,
|
|
|
|
record *models.Verification,
|
|
|
|
innFH, ruleFH, egruleFH, certFH *multipart.FileHeader) (*models.Verification, error) {
|
|
|
|
now := time.Now()
|
|
|
|
record.ID = primitive.NewObjectIDFromTimestamp(now).Hex()
|
|
|
|
record.UpdatedAt = now
|
|
|
|
|
|
|
|
// Put inn file
|
|
|
|
inn, err := innFH.Open()
|
|
|
|
if r.err(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = r.s3.PutObject(ctx, VerificationBucket, fmt.Sprintf("%s/%s", userID, innFH.Filename), inn, innFH.Size, minio.PutObjectOptions{})
|
|
|
|
if r.err(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rule, err := ruleFH.Open()
|
|
|
|
if r.err(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put rule file
|
|
|
|
_, err = r.s3.PutObject(ctx, VerificationBucket, fmt.Sprintf("%s/%s", userID, ruleFH.Filename), rule, ruleFH.Size, minio.PutObjectOptions{})
|
|
|
|
if r.err(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put egrule file
|
|
|
|
egrule, err := egruleFH.Open()
|
|
|
|
if r.err(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = r.s3.PutObject(ctx, VerificationBucket, fmt.Sprintf("%s/%s", userID, egruleFH.Filename), egrule, egruleFH.Size, minio.PutObjectOptions{})
|
|
|
|
if r.err(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put certificate file
|
|
|
|
if certFH != nil {
|
|
|
|
cert, err := certFH.Open()
|
|
|
|
if r.err(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = r.s3.PutObject(ctx, VerificationBucket, fmt.Sprintf("%s/%s", userID, certFH.Filename), cert, certFH.Size, minio.PutObjectOptions{})
|
|
|
|
if r.err(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-09-15 00:39:38 +00:00
|
|
|
record.Files = []models.VerificationFile{
|
2023-06-12 14:19:10 +00:00
|
|
|
{
|
|
|
|
Name: "certificate",
|
2023-07-18 22:32:20 +00:00
|
|
|
Url: fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, certFH.Filename),
|
2023-06-12 14:19:10 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert to MongoDB
|
2023-09-15 00:39:38 +00:00
|
|
|
record.Files = append(record.Files, []models.VerificationFile{
|
2023-06-12 14:19:10 +00:00
|
|
|
{
|
|
|
|
Name: "inn",
|
2023-07-18 22:32:20 +00:00
|
|
|
Url: fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, innFH.Filename),
|
2023-06-12 14:19:10 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "rule",
|
2023-07-18 22:32:20 +00:00
|
|
|
Url: fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, ruleFH.Filename),
|
2023-06-12 14:19:10 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "egrule",
|
2023-07-18 22:32:20 +00:00
|
|
|
Url: fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, egruleFH.Filename),
|
2023-06-12 14:19:10 +00:00
|
|
|
},
|
|
|
|
}...)
|
|
|
|
|
|
|
|
result, err := r.mongo.InsertOne(ctx, record)
|
|
|
|
if r.err(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
record.ID = result.InsertedID.(string)
|
|
|
|
return record, nil
|
|
|
|
}
|
|
|
|
|
2023-09-15 00:39:38 +00:00
|
|
|
func (r *VerificationRepository) GetByUserID(ctx context.Context, userID string) (*models.Verification, error) {
|
2023-06-12 14:19:10 +00:00
|
|
|
if userID == "" {
|
|
|
|
err := errors.New("userID cannot be empty")
|
|
|
|
r.logger.Error("VerificationRepositoryError", zap.Error(err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
filter := bson.M{
|
|
|
|
"user_id": userID,
|
|
|
|
}
|
|
|
|
|
2023-07-19 13:08:58 +00:00
|
|
|
opts := options.FindOne().SetSort(bson.D{{Key: "updated_at", Value: -1}})
|
|
|
|
|
2023-06-12 14:19:10 +00:00
|
|
|
var result models.Verification
|
2023-07-19 13:08:58 +00:00
|
|
|
err := r.mongo.FindOne(ctx, filter, opts).Decode(&result)
|
2023-06-12 14:19:10 +00:00
|
|
|
if err != nil {
|
|
|
|
if err == mongo.ErrNoDocuments {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
r.logger.Error("VerificationRepositoryError", zap.Error(err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *VerificationRepository) Get(ctx context.Context, id string) (*models.Verification, error) {
|
|
|
|
if id == "" {
|
|
|
|
err := errors.New("_id cannot be empty")
|
|
|
|
r.logger.Error("VerificationRepositoryError", zap.Error(err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
filter := bson.M{
|
|
|
|
"_id": id,
|
|
|
|
}
|
|
|
|
|
|
|
|
var result models.Verification
|
|
|
|
err := r.mongo.FindOne(ctx, filter).Decode(&result)
|
|
|
|
if err != nil {
|
|
|
|
if err == mongo.ErrNoDocuments {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
r.logger.Error("VerificationRepositoryError", zap.Error(err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *VerificationRepository) Update(ctx context.Context, record *models.Verification) (*models.Verification, error) {
|
|
|
|
record.UpdatedAt = time.Now()
|
|
|
|
|
|
|
|
var result models.Verification
|
|
|
|
err := r.mongo.FindOneAndUpdate(ctx, bson.M{"_id": record.ID}, bson.M{"$set": record}, options.FindOneAndUpdate().SetReturnDocument(options.After)).Decode(&result)
|
|
|
|
if r.err(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &result, nil
|
|
|
|
}
|
2023-09-15 00:39:38 +00:00
|
|
|
|
|
|
|
func (r *VerificationRepository) UpdateFile(ctx context.Context, userID, fileName string, fileHeader *multipart.FileHeader) error {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// put file
|
|
|
|
fileReader, err := fileHeader.Open()
|
|
|
|
if r.err(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = r.s3.PutObject(ctx, VerificationBucket, fmt.Sprintf("%s/%s", userID, fileHeader.Filename), fileReader, fileHeader.Size, minio.PutObjectOptions{})
|
|
|
|
if r.err(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fileUrl := fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, fileHeader.Filename)
|
|
|
|
|
|
|
|
// remove old file
|
|
|
|
verification, err := r.GetByUserID(ctx, userID)
|
|
|
|
if r.err(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
found := false
|
|
|
|
for iterator, file := range verification.Files {
|
|
|
|
if file.Name != fileName {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
objectName := strings.ReplaceAll(file.Name, fmt.Sprintf("%v/%v/", VerificationEndpointURL, VerificationBucket), "")
|
|
|
|
if err = r.s3.RemoveObject(ctx, VerificationBucket, objectName, minio.RemoveObjectOptions{}); r.err(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
verification.Files[iterator] = models.VerificationFile{Name: file.Name, Url: fileUrl}
|
|
|
|
found = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
verification.Files = append(verification.Files, models.VerificationFile{Name: fileName, Url: fileUrl})
|
|
|
|
}
|
|
|
|
|
|
|
|
// update in mongodb
|
|
|
|
_, err = r.Update(ctx, &models.Verification{ID: verification.ID, Files: verification.Files})
|
|
|
|
if r.err(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-12 14:19:10 +00:00
|
|
|
func (r *VerificationRepository) err(err error) bool {
|
|
|
|
if err != nil {
|
|
|
|
r.logger.Error("VerificationRepositoryError", zap.Error(err))
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|