Skip to content

Commit

Permalink
Merge pull request #64 from go-park-mail-ru/fix-final
Browse files Browse the repository at this point in the history
Исправление полных путей, контроль размера картинок, контроль типа файлов, доработка html для pdf
  • Loading branch information
bqback authored Dec 20, 2024
2 parents cbe37b9 + 8232d03 commit 8861b17
Show file tree
Hide file tree
Showing 13 changed files with 178 additions and 132 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# 2024_2_VKatuny
![Coverage](https://img.shields.io/badge/Coverage-60.8%25-yellow)
![Coverage](https://img.shields.io/badge/Coverage-60.3%25-yellow)

Данный репозиторий предназначен для хранения backend части проекта HeadHunter,
разрабатываемого командой VKатуны.
Expand Down
38 changes: 19 additions & 19 deletions db/migrations/025_testdata.sql
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,24 @@ insert into vacancy (employer_id , salary, position, vacancy_description, work_t
values (1, 90000, 'Скульптор', 'Требуется скульптор без опыта работы', 1, 1);


insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id)
values (1, 170000, 'Дизайнер витрины', 'Требуется оформить главный стенд нового офиса', 2, 2);
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id,city_id)
values (3, 80000, 'Младший дизайнер интерьера', 'Требуется специалист в области дизайна интерьера в дружный коллектив нашего бюро', 1, 2);
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id)
values (3, 100000, 'Художник оформитель', 'Требуется опытный специалист для оформления дизайнерской мебели', 1, 3);
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id)
values (2, 210000, 'Младший ландшафтный дизайнер', 'Нанимаем опытного специалиста для оформления нескольких парковых зон', 2, 1);
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id)
values (2, 120000, 'Младший ландшафтный дизайнер', 'Требуется специалист по живой изгороди', 1, 3);
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id,city_id)
values (2, 220000, 'Куратор', 'Требуется куратор в нашу программу повышения квалификации сотрудников', 1, 2);
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id)
values (1, 320000, 'Графитист', 'Нанимаем профессионального художника-гафитиста для временного графити приуроченному к празднику на высотном здании', 2, 2);
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id)
values (4, 110000, 'Хореограф', 'Ищем профессионального хареографа со стажем 15 лет', 1, 3);
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id)
values (4, 210000, 'Художник декоратор', 'Требуется декоратор для постановок', 1, 1);
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id, path_to_company_avatar)
values (1, 170000, 'Дизайнер витрины', 'Требуется оформить главный стенд нового офиса', 2, 2, "media/Uncompressed/1ahsdfhtrgtorhjertoldbtsdjgxsdfkg.PNG");
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id, path_to_company_avatar)
values (3, 80000, 'Младший дизайнер интерьера', 'Требуется специалист в области дизайна интерьера в дружный коллектив нашего бюро', 1, 2, "media/Uncompressed/1ahsdfhtrgtaahjertoldbtsdjgxsdfkg.PNG");
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id, path_to_company_avatar)
values (3, 100000, 'Художник оформитель', 'Требуется опытный специалист для оформления дизайнерской мебели', 1, 3, "media/Uncompressed/1ahsdfhtrgtaahjertoldbtsdjgxsdfkg.PNG");
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id, path_to_company_avatar)
values (2, 210000, 'Младший ландшафтный дизайнер', 'Нанимаем опытного специалиста для оформления нескольких парковых зон', 2, 1, "media/Uncompressed/1ahsdfhtrgtorhjruooldbtsdjgxsdfkg.JPG");
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id, path_to_company_avatar)
values (2, 120000, 'Младший ландшафтный дизайнер', 'Требуется специалист по живой изгороди', 1, 3, "media/Uncompressed/1ahsdfhtrgtorhjruooldbtsdjgxsdfkg.JPG");
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id, path_to_company_avatar)
values (2, 220000, 'Куратор', 'Требуется куратор в нашу программу повышения квалификации сотрудников', 1, 2, "media/Uncompressed/1ahsdfhtrgtorhjruooldbtsdjgxsdfkg.JPG");
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id, path_to_company_avatar)
values (1, 320000, 'Графитист', 'Нанимаем профессионального художника-гафитиста для временного графити приуроченному к празднику на высотном здании', 2, 2, "media/Uncompressed/1ahsdfhtrgtorhjertoldbtsdjgxsdfkg.PNG");
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id, path_to_company_avatar)
values (4, 110000, 'Хореограф', 'Ищем профессионального хареографа со стажем 15 лет', 1, 3, "media/Uncompressed/1ahsdfhtrgtorhjruaaldbtsdjgxsdfkg.JPG");
insert into vacancy (employer_id , salary, position, vacancy_description, work_type_id, city_id, path_to_company_avatar)
values (4, 210000, 'Художник декоратор', 'Требуется декоратор для постановок', 1, 1, "media/Uncompressed/1ahsdfhtrgtorhjruaaldbtsdjgxsdfkg.JPG");


insert into vacancy_subscriber (applicant_id , vacancy_id) values (1, 1);
Expand Down Expand Up @@ -119,4 +119,4 @@ delete from vacancy_to_creation_tag where vacancy_id=1 and creation_tag_id=1;
delete from employer_rate_to_applicant_creation where employer_id=1 and applicant_creation_id=1;
delete from cv_to_creation_tag where cv_id=1 and creation_tag_id=1;
delete from cv_to_creation_tag where applicant_creation_id=1 and creation_tag_id=1;
delete from cv_to_creation_tag where applicant_creation_id=1 and portfolio_id=1;
delete from cv_to_creation_tag where applicant_creation_id=1 and portfolio_id=1;
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/davidbyttow/govips/v2 v2.15.0
github.com/gorilla/websocket v1.5.3
github.com/jackc/pgx/v5 v5.7.1
github.com/mailru/easyjson v0.9.0
github.com/rafaeljusto/redigomock/v3 v3.1.2
github.com/stretchr/testify v1.10.0
github.com/swaggo/swag v1.16.4
Expand Down Expand Up @@ -40,7 +41,6 @@ require (
github.com/gorilla/css v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.61.0 // indirect
Expand Down
14 changes: 12 additions & 2 deletions internal/pkg/applicant/delivery/applicant_profile.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package delivery

import (
"io"
"net/http"
"strconv"

Expand Down Expand Up @@ -125,7 +126,7 @@ func (h *ApplicantHandlers) UpdateProfile(w http.ResponseWriter, r *http.Request
})
return
}

r.ParseMultipartForm(25 << 20) // 25Mb
newProfileData := &dto.JSONUpdateApplicantProfile{}
newProfileData.FirstName = r.FormValue("firstName")
newProfileData.LastName = r.FormValue("lastName")
Expand All @@ -137,7 +138,16 @@ func (h *ApplicantHandlers) UpdateProfile(w http.ResponseWriter, r *http.Request
file, header, err := r.FormFile("profile_avatar")
if err == nil {
defer file.Close()
fileAddress, compressedFileAddress, err := h.fileLoadingUsecase.WriteImage(file, header)
fileWasRead, err := io.ReadAll(file)
if err != nil {
h.logger.Errorf("function %s: got err %s", fn, err)
middleware.UniversalMarshal(w, http.StatusInternalServerError, dto.JSONResponse{
HTTPStatus: http.StatusInternalServerError,
Error: err.Error(),
})
return
}
fileAddress, compressedFileAddress, err := h.fileLoadingUsecase.WriteImage(fileWasRead, header)
h.logger.Debugf("address %s compressed %s", fileAddress, compressedFileAddress)
if err != nil {
middleware.UniversalMarshal(w, http.StatusBadRequest, dto.JSONResponse{
Expand Down
6 changes: 0 additions & 6 deletions internal/pkg/applicant/usecase/applicant_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,6 @@ func (au *ApplicantUsecase) UpdateApplicantProfile(ctx context.Context, applican
return err
}
au.logger.Debug("compress")
// TODO: add microservice

// if err != nil {
// au.logger.Errorf("fail compress microservice")
// return err
// }

au.logger.Debugf("function: %s; successfully updated applicant profile", fn)
return nil
Expand Down
27 changes: 23 additions & 4 deletions internal/pkg/cvs/delivery/cvs_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package delivery

import (
"context"
"io"
"net/http"
"strconv"

Expand Down Expand Up @@ -84,7 +85,16 @@ func (h *CVsHandler) CreateCV(w http.ResponseWriter, r *http.Request) {
file, header, err := r.FormFile("profile_avatar")
if err == nil {
defer file.Close()
fileAddress, compressedFileAddress, err := h.fileLoadingUsecase.WriteImage(file, header)
fileWasRead, err := io.ReadAll(file)
if err != nil {
h.logger.Errorf("function %s: got err %s", fn, err)
middleware.UniversalMarshal(w, http.StatusInternalServerError, dto.JSONResponse{
HTTPStatus: http.StatusInternalServerError,
Error: err.Error(),
})
return
}
fileAddress, compressedFileAddress, err := h.fileLoadingUsecase.WriteImage(fileWasRead, header)
if err != nil {
middleware.UniversalMarshal(w, http.StatusBadRequest, dto.JSONResponse{
HTTPStatus: http.StatusBadRequest,
Expand Down Expand Up @@ -164,7 +174,7 @@ func (h *CVsHandler) GetCV(w http.ResponseWriter, r *http.Request) {
})
return
}
CV.CompressedAvatar = h.fileLoadingUsecase.FindCompressedFile(CV.Avatar)
CV.CompressedAvatar = h.fileLoadingUsecase.FindCompressedFile(CV.Avatar)
h.logger.Debugf("%s: success, got cv: %v", fn, CV)
middleware.UniversalMarshal(w, http.StatusOK, dto.JSONResponse{
HTTPStatus: http.StatusOK,
Expand Down Expand Up @@ -236,7 +246,16 @@ func (h *CVsHandler) UpdateCV(w http.ResponseWriter, r *http.Request) {
file, header, err := r.FormFile("profile_avatar")
if err == nil {
defer file.Close()
fileAddress, compressedFileAddress, err := h.fileLoadingUsecase.WriteImage(file, header)
fileWasRead, err := io.ReadAll(file)
if err != nil {
h.logger.Errorf("function %s: got err %s", fn, err)
middleware.UniversalMarshal(w, http.StatusInternalServerError, dto.JSONResponse{
HTTPStatus: http.StatusInternalServerError,
Error: err.Error(),
})
return
}
fileAddress, compressedFileAddress, err := h.fileLoadingUsecase.WriteImage(fileWasRead, header)
if err != nil {
middleware.UniversalMarshal(w, http.StatusBadRequest, dto.JSONResponse{
HTTPStatus: http.StatusBadRequest,
Expand Down Expand Up @@ -372,7 +391,7 @@ func (h *CVsHandler) CVtoPDF(w http.ResponseWriter, r *http.Request) {
})
return
}
name.FileName="/"+name.FileName
name.FileName = "/" + name.FileName
middleware.UniversalMarshal(w, http.StatusOK, dto.JSONResponse{
HTTPStatus: http.StatusOK,
Body: name,
Expand Down
18 changes: 14 additions & 4 deletions internal/pkg/employer/delivery/employer_profile.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package delivery

import (
"io"
"net/http"
"strconv"

Expand Down Expand Up @@ -122,7 +123,7 @@ func (h *EmployerHandlers) UpdateProfile(w http.ResponseWriter, r *http.Request)
})
return
}

r.ParseMultipartForm(25 << 20) // 25Mb
newProfileData := &dto.JSONUpdateEmployerProfile{}
newProfileData.FirstName = r.FormValue("firstName")
newProfileData.LastName = r.FormValue("lastName")
Expand All @@ -132,7 +133,16 @@ func (h *EmployerHandlers) UpdateProfile(w http.ResponseWriter, r *http.Request)
file, header, err := r.FormFile("profile_avatar")
if err == nil {
defer file.Close()
fileAddress, compressedFileAddress, err := h.fileLoadingUsecase.WriteImage(file, header)
fileWasRead, err := io.ReadAll(file)
if err != nil {
h.logger.Errorf("function %s: got err %s", fn, err)
middleware.UniversalMarshal(w, http.StatusInternalServerError, dto.JSONResponse{
HTTPStatus: http.StatusInternalServerError,
Error: err.Error(),
})
return
}
fileAddress, compressedFileAddress, err := h.fileLoadingUsecase.WriteImage(fileWasRead, header)
if err != nil {
middleware.UniversalMarshal(w, http.StatusBadRequest, dto.JSONResponse{
HTTPStatus: http.StatusBadRequest,
Expand Down Expand Up @@ -188,7 +198,7 @@ func (h *EmployerHandlers) GetEmployerVacancies(w http.ResponseWriter, r *http.R
return
}

vacancies, err := h.vacanciesUsecase.GetVacanciesByEmployerID(r.Context(),employerID)
vacancies, err := h.vacanciesUsecase.GetVacanciesByEmployerID(r.Context(), employerID)
if err != nil {
h.logger.Errorf("function %s: got err %s", fn, err)
middleware.UniversalMarshal(w, http.StatusInternalServerError, dto.JSONResponse{
Expand All @@ -200,7 +210,7 @@ func (h *EmployerHandlers) GetEmployerVacancies(w http.ResponseWriter, r *http.R

for _, vacancy := range vacancies {
utils.EscapeHTMLStruct(vacancy)
}
}

h.logger.Debugf("function %s: success, got vacancies: %d", fn, len(vacancies))
middleware.UniversalMarshal(w, http.StatusOK, dto.JSONResponse{
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/file_loading/file_loading.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (
)

type IFileLoadingRepository interface {
WriteFileOnDisk(filename string, header *multipart.FileHeader, file multipart.File) (string, string, error)
WriteFileOnDisk(filename string, header *multipart.FileHeader, file []byte) (string, string, error)
CVtoPDF(CV *dto.JSONCv, applicant *dto.JSONGetApplicantProfile) (string, error)
}

type IFileLoadingUsecase interface {
WriteImage(file multipart.File, header *multipart.FileHeader) (string, string, error)
WriteImage(file []byte, header *multipart.FileHeader) (string, string, error)
FindCompressedFile(filename string) string
CVtoPDF(CV *dto.JSONCv, applicant *dto.JSONGetApplicantProfile) (*dto.CVPDFFile, error)
}
4 changes: 2 additions & 2 deletions internal/pkg/file_loading/mock/file_loading.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 30 additions & 29 deletions internal/pkg/file_loading/repository/file_loading_file_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"mime/multipart"
"os"
"slices"
"strconv"
"strings"
"text/template"
Expand All @@ -31,7 +32,7 @@ func NewFileLoadingStorage(logger *logrus.Logger, mediaDir, CVinPDFDir, template
}
}

func (s *FileLoadingStorage) WriteFileOnDisk(filename string, header *multipart.FileHeader, file multipart.File) (string, string, error) {
func (s *FileLoadingStorage) WriteFileOnDisk(filename string, header *multipart.FileHeader, file []byte) (string, string, error) {
fn := "FileLoadingStorage.WriteFileOnDisk"
s.logger.Debugf("%s: entering with name: %s", fn, s.mediaDir+filename+header.Filename)
dst, err := os.Create(s.mediaDir + filename + header.Filename)
Expand All @@ -40,61 +41,54 @@ func (s *FileLoadingStorage) WriteFileOnDisk(filename string, header *multipart.
return "", "", fmt.Errorf("error creating file")
}
defer dst.Close()
if _, err := io.Copy(dst, file); err != nil {
reader := bytes.NewReader(file)
if _, err := io.Copy(dst, reader); err != nil {
s.logger.Errorf("%s: got error copying file", fn)
return "", "", fmt.Errorf("error copying file")
}
s.logger.Debugf("%s: done with name: %s and %s", fn, s.mediaDir, filename+header.Filename)
return s.mediaDir, filename + header.Filename, nil
dirList := strings.Split(s.mediaDir, "/")
dirList = dirList[slices.Index(dirList, "2024_2_VKatuny")+1:]
dirCut := strings.Join(dirList, "/") + "/"
return dirCut, filename + header.Filename, nil
}

func (s *FileLoadingStorage) CVtoPDF(CV *dto.JSONCv, applicant *dto.JSONGetApplicantProfile) (string, error) {
fn := "FileLoadingStorage.CVtoPDF"
s.logger.Debugf("%s: entering", fn)

pwd, _ := os.Getwd()
newPwd := ""
for _, i := range strings.Split(pwd, "/") {
newPwd += i + "/"
if i == "2024_2_VKatuny" {
break
}
}
tmpl := template.Must(template.ParseFiles(newPwd + s.templateDir + "template.html"))
pwd, err := os.Getwd()
if err != nil {
s.logger.Errorf("%s: got err %s", fn, err)
return "", err
}
templateDir := addslesh(s.templateDir)
mediaDir := addslesh(s.mediaDir)
cvinPDFdir := addslesh(s.cvinPDFdir)
tmpl := template.Must(template.ParseFiles(templateDir + "template.html"))
type And struct {
CV dto.JSONCv
Applicant dto.JSONGetApplicantProfile
IsImg int
Template string
}
megaStruct := And{CV: *CV, Applicant: *applicant}
if len(megaStruct.Applicant.BirthDate) > 9 {
megaStruct.Applicant.BirthDate = megaStruct.Applicant.BirthDate[:9]
if len(megaStruct.Applicant.BirthDate) > 10 {
megaStruct.Applicant.BirthDate = megaStruct.Applicant.BirthDate[:10]
}
if len(megaStruct.CV.CreatedAt) > 9 {
megaStruct.CV.CreatedAt = megaStruct.CV.CreatedAt[:9]
if len(megaStruct.CV.CreatedAt) > 10 {
megaStruct.CV.CreatedAt = megaStruct.CV.CreatedAt[:10]
}
s.logger.Debugf("avatar: %s", pwd+CV.Avatar)
s.logger.Debugf("template: %s", pwd+"/"+s.templateDir+"template.css")
megaStruct.Template = pwd + "/" + s.templateDir + "template.css"
megaStruct.CV.Avatar = pwd + CV.Avatar
s.logger.Debugf("avatar: %s", mediaDir+CV.Avatar)
s.logger.Debugf("template: %s", templateDir+"template.css")
megaStruct.Template = templateDir + "template.css"
megaStruct.CV.Avatar = mediaDir + strings.Split(CV.Avatar, "/")[len(strings.Split(CV.Avatar, "/"))-1]
if CV.Avatar != "" {
megaStruct.IsImg = 1
} else {
megaStruct.IsImg = 0
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, megaStruct)
err := tmpl.Execute(&buf, megaStruct)
if err != nil {
s.logger.Errorf("%s: got err %s", fn, err)
return "", err
}
//s.logger.Debugf(buf.String())

pdfg, err := wkhtml.NewPDFGenerator()
if err != nil {
Expand All @@ -109,12 +103,19 @@ func (s *FileLoadingStorage) CVtoPDF(CV *dto.JSONCv, applicant *dto.JSONGetAppli
s.logger.Errorf("%s: got err %s", fn, err)
return "", err
}
name := s.cvinPDFdir + strconv.Itoa(int(CV.ID)) + "&&" + strconv.Itoa(int(CV.ApplicantID)) + ".pdf"
err = pdfg.WriteFile(newPwd+name)
name := cvinPDFdir + strconv.Itoa(int(CV.ID)) + "&&" + strconv.Itoa(int(CV.ApplicantID)) + ".pdf"
err = pdfg.WriteFile(name)
if err != nil {
s.logger.Errorf("%s: got err %s", fn, err)
return "", err
}
s.logger.Debugf("%s: done with name: %s", fn, name)
return name, nil
}

func addslesh(str string) string {
if str[len(str)-1] != '/' {
str += "/"
}
return str
}
Loading

0 comments on commit 8861b17

Please sign in to comment.