package db import ( "context" "crypto/sha256" "database/sql" "encoding/hex" "errors" "fmt" "io" "mime/multipart" "net/http" "os" "path" "path/filepath" ) type CreateDocumentTxParams struct { AccountID uint64 `json:"account_id"` PersonID uint64 `json:"person_id"` MailID uint64 `json:"mail_id"` File *multipart.FileHeader `json:"file"` Creator string `json:"creator"` } type CreateDocumentTxResult struct { Document Document `json:"document"` } func (store *SQLStore) CreateDocumentTx(ctx context.Context, arg CreateDocumentTxParams) (doc Document, code int, err error) { var result CreateDocumentTxResult if arg.MailID > 0 && arg.PersonID > 0 { return Document{}, http.StatusBadRequest, errors.New("document can't be assigned to both person_id AND mail_id") } if arg.MailID < 1 && arg.PersonID < 1 { return Document{}, http.StatusBadRequest, errors.New("document has to be assigned to either a person_id or a mail_id") } req := CreateDocumentParams{ Creator: arg.Creator, } targetDir := filepath.Join("./files", fmt.Sprintf("%d", arg.AccountID)) fileData, err := arg.File.Open() if err != nil { return Document{}, http.StatusBadRequest, errors.New("failed to read file") } h := sha256.New() _, err = io.Copy(h, fileData) if err != nil { return Document{}, http.StatusInternalServerError, errors.New("could not create file hash") } fileData.Seek(0, io.SeekStart) req.Hash = hex.EncodeToString(h.Sum(nil)) if arg.MailID > 0 { _, err := store.GetMail(ctx, arg.MailID) if err != nil { return Document{}, http.StatusNotFound, errors.New("mail not found") } targetDir = filepath.Join(targetDir, "mail", fmt.Sprintf("%d", arg.MailID)) req.MailID = sql.NullInt64{ Valid: true, Int64: int64(arg.MailID), } } if arg.PersonID > 0 { _, err := store.GetPerson(ctx, arg.PersonID) if err != nil { return Document{}, http.StatusNotFound, errors.New("person not found") } docs, err := store.GetDocumentByHash(ctx, GetDocumentByHashParams{ AccountID: arg.AccountID, Hash: req.Hash, }) if err != nil { return Document{}, http.StatusInternalServerError, fmt.Errorf("could not check file hash in db: %v", err.Error()) } if len(docs) > 0 { return Document{ ID: docs[0], }, http.StatusConflict, errors.New("file already exists in database") } targetDir = filepath.Join(targetDir, "person", fmt.Sprintf("%d", arg.PersonID)) req.PersonID = sql.NullInt64{ Valid: true, Int64: int64(arg.PersonID), } } req.Type = path.Ext(arg.File.Filename) req.Name = arg.File.Filename p := filepath.Join(targetDir, req.Hash+path.Ext(arg.File.Filename)) req.Path = p if _, err := os.Stat(p); err == nil { return Document{}, http.StatusConflict, errors.New("file already exists") } err = store.execTx(ctx, func(q *Queries) error { var err error if _, err := os.Stat(targetDir); err != nil { err = os.MkdirAll(targetDir, 0755) if err != nil { return errors.New("could not create directory structure") } } f, err := os.Create(p) if err != nil { return fmt.Errorf("could not create file: %v", err) } _, err = io.Copy(f, fileData) if err != nil { return errors.New("could not write file") } result.Document, err = q.CreateDocument(ctx, req) if err != nil { return err } return err }) return result.Document, http.StatusInternalServerError, err }