2025-02-07 12:12:51 +00:00
|
|
|
package documentclean
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2025-03-03 21:56:17 +00:00
|
|
|
"database/sql"
|
|
|
|
|
"errors"
|
2025-02-28 13:11:53 +00:00
|
|
|
"fmt"
|
2025-02-11 15:22:59 +00:00
|
|
|
"log/slog"
|
2025-03-05 12:05:46 +00:00
|
|
|
|
2025-02-07 12:12:51 +00:00
|
|
|
"queryorchestration/internal/database/repository"
|
2025-04-02 18:50:03 +00:00
|
|
|
documenttypes "queryorchestration/internal/document/types"
|
2025-03-11 18:15:49 +00:00
|
|
|
"queryorchestration/internal/serviceconfig/build"
|
2025-04-03 19:17:24 +00:00
|
|
|
"queryorchestration/internal/serviceconfig/objectstore"
|
2025-02-07 12:12:51 +00:00
|
|
|
|
2025-02-28 13:11:53 +00:00
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
2025-02-07 12:12:51 +00:00
|
|
|
"github.com/google/uuid"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type CleanParams struct {
|
2025-04-03 19:17:24 +00:00
|
|
|
ID uuid.UUID
|
|
|
|
|
Hash string
|
|
|
|
|
Bucket string
|
|
|
|
|
Key objectstore.BucketKey
|
2025-02-07 12:12:51 +00:00
|
|
|
}
|
|
|
|
|
|
2025-02-28 13:11:53 +00:00
|
|
|
type ExecuteCleanResponse struct {
|
2025-04-03 19:17:24 +00:00
|
|
|
key *objectstore.BucketKey
|
|
|
|
|
bucket *string
|
2025-04-02 18:50:03 +00:00
|
|
|
mimetype *documenttypes.MimeType
|
|
|
|
|
hash *string
|
|
|
|
|
failReason *documenttypes.InvalidDocumentReason
|
2025-02-28 13:11:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Service) executeCleanTasks(ctx context.Context, params *CleanParams) (*ExecuteCleanResponse, error) {
|
2025-04-03 19:17:24 +00:00
|
|
|
keyStr := params.Key.String()
|
2025-02-28 13:11:53 +00:00
|
|
|
out, err := s.cfg.GetStoreClient().HeadObject(ctx, &s3.HeadObjectInput{
|
2025-04-03 19:17:24 +00:00
|
|
|
Bucket: ¶ms.Bucket,
|
|
|
|
|
Key: &keyStr,
|
2025-02-28 13:11:53 +00:00
|
|
|
IfMatch: ¶ms.Hash,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
length := int64(1)
|
|
|
|
|
if out.ContentLength != nil {
|
|
|
|
|
length = *out.ContentLength
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mimeType, err := s.getAcceptedMimeType(ctx, params, out.ContentType, length)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2025-04-02 18:50:03 +00:00
|
|
|
} else if mimeType == documenttypes.MimeTypeInvalid {
|
|
|
|
|
reason := documenttypes.InvalidDocumentMimeType
|
2025-02-28 13:11:53 +00:00
|
|
|
return &ExecuteCleanResponse{
|
|
|
|
|
failReason: &reason,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-22 14:40:16 +00:00
|
|
|
content, err := documenttypes.GetFile(ctx, s.cfg, documenttypes.GetFileParams{
|
|
|
|
|
Bucket: params.Bucket,
|
|
|
|
|
Hash: params.Hash,
|
|
|
|
|
Key: params.Key,
|
|
|
|
|
Mimetype: mimeType,
|
|
|
|
|
})
|
2025-02-28 13:11:53 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-02 18:50:03 +00:00
|
|
|
corruptReason := content.IsCorrupt(ctx)
|
2025-02-28 13:11:53 +00:00
|
|
|
if corruptReason != nil {
|
|
|
|
|
return &ExecuteCleanResponse{
|
|
|
|
|
failReason: corruptReason,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &ExecuteCleanResponse{
|
|
|
|
|
mimetype: &mimeType,
|
2025-04-03 19:17:24 +00:00
|
|
|
bucket: ¶ms.Bucket,
|
|
|
|
|
key: ¶ms.Key,
|
2025-04-02 18:50:03 +00:00
|
|
|
hash: ¶ms.Hash,
|
2025-02-28 13:11:53 +00:00
|
|
|
}, nil
|
2025-02-07 12:12:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Service) clean(ctx context.Context, id uuid.UUID) error {
|
2025-02-11 15:22:59 +00:00
|
|
|
slog.Debug("cleaning document", "id", id.String())
|
2025-03-20 11:06:41 +00:00
|
|
|
docId := id
|
2025-02-07 12:12:51 +00:00
|
|
|
|
2025-03-17 18:14:15 +00:00
|
|
|
doc, err := s.cfg.GetDBQueries().GetDocumentSummary(ctx, docId)
|
2025-02-28 13:11:53 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-07 12:12:51 +00:00
|
|
|
entry, err := s.cfg.GetDBQueries().GetDocumentEntry(ctx, docId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-03 19:17:24 +00:00
|
|
|
key, err := objectstore.ParseBucketKey(entry.Key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-28 13:11:53 +00:00
|
|
|
out, err := s.executeCleanTasks(ctx, &CleanParams{
|
2025-04-03 19:17:24 +00:00
|
|
|
ID: id,
|
|
|
|
|
Hash: doc.Hash,
|
|
|
|
|
Bucket: entry.Bucket,
|
|
|
|
|
Key: key,
|
2025-02-07 12:12:51 +00:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-03 21:56:17 +00:00
|
|
|
err = s.storeClean(ctx, id, out)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Service) storeClean(ctx context.Context, id uuid.UUID, out *ExecuteCleanResponse) error {
|
2025-03-20 11:06:41 +00:00
|
|
|
docId := id
|
2025-03-11 18:15:49 +00:00
|
|
|
version := build.GetVersionUnixTimestamp()
|
2025-02-07 12:12:51 +00:00
|
|
|
|
2025-03-03 21:56:17 +00:00
|
|
|
params := &repository.AddDocumentCleanParams{
|
2025-02-07 12:12:51 +00:00
|
|
|
Documentid: docId,
|
2025-02-28 13:11:53 +00:00
|
|
|
}
|
|
|
|
|
if out.failReason != nil {
|
|
|
|
|
slog.Info("Failed document", "id", id, "reason", *out.failReason)
|
2025-04-02 18:50:03 +00:00
|
|
|
params.Fail = documenttypes.ToDBNullFailType(*out.failReason)
|
2025-02-28 13:11:53 +00:00
|
|
|
} else {
|
2025-04-03 19:17:24 +00:00
|
|
|
params.Bucket = out.bucket
|
|
|
|
|
key := out.key.String()
|
|
|
|
|
params.Key = &key
|
2025-04-02 18:50:03 +00:00
|
|
|
params.Hash = out.hash
|
|
|
|
|
params.Mimetype = documenttypes.ToDBNullMimeType(*out.mimetype)
|
2025-02-28 13:11:53 +00:00
|
|
|
}
|
|
|
|
|
|
2025-03-03 21:56:17 +00:00
|
|
|
err := s.cfg.ExecuteDBTransaction(ctx, func(ctx context.Context, q *repository.Queries) error {
|
|
|
|
|
lastClean, err := q.GetMostRecentDocumentCleanEntry(ctx, docId)
|
|
|
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newID := lastClean.ID
|
|
|
|
|
if s.isNewClean(lastClean, out) {
|
|
|
|
|
id, err := q.AddDocumentClean(ctx, params)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newID = id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = q.AddDocumentCleanEntry(ctx, &repository.AddDocumentCleanEntryParams{
|
|
|
|
|
Cleanid: newID,
|
|
|
|
|
Version: version,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
})
|
2025-02-07 12:12:51 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-28 13:11:53 +00:00
|
|
|
if out.failReason != nil {
|
|
|
|
|
return fmt.Errorf("%s", *out.failReason)
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-07 12:12:51 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2025-03-03 21:56:17 +00:00
|
|
|
|
|
|
|
|
func (s *Service) isNewClean(lastEntry *repository.GetMostRecentDocumentCleanEntryRow, out *ExecuteCleanResponse) bool {
|
2025-03-20 11:06:41 +00:00
|
|
|
if lastEntry == nil {
|
2025-03-03 21:56:17 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if out.failReason != nil {
|
2025-04-02 18:50:03 +00:00
|
|
|
return *out.failReason != documenttypes.ParseDBNullFailType(lastEntry.Fail)
|
2025-03-03 21:56:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if lastEntry.Fail.Valid {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-03 19:17:24 +00:00
|
|
|
if out.bucket == nil || out.key == nil || out.mimetype == nil || lastEntry.Bucket == nil || lastEntry.Key == nil {
|
2025-03-20 11:06:41 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-03 19:17:24 +00:00
|
|
|
return !(*out.bucket == *lastEntry.Bucket &&
|
|
|
|
|
out.key.String() == *lastEntry.Key &&
|
2025-04-02 18:50:03 +00:00
|
|
|
*out.mimetype == documenttypes.ParseDBNullMimeType(lastEntry.Mimetype))
|
2025-03-03 21:56:17 +00:00
|
|
|
}
|