diff --git a/internal/app/club.go b/internal/app/club.go index a0a543d..36ff85c 100644 --- a/internal/app/club.go +++ b/internal/app/club.go @@ -8,6 +8,7 @@ import ( "github.com/STUD-IT-team/bmstu-stud-web-backend/internal/domain" "github.com/STUD-IT-team/bmstu-stud-web-backend/internal/domain/requests" "github.com/STUD-IT-team/bmstu-stud-web-backend/internal/domain/responses" + "github.com/STUD-IT-team/bmstu-stud-web-backend/internal/infrastructure/postgres" ) type clubStorage interface { @@ -26,6 +27,10 @@ type clubStorage interface { AddOrgs(ctx context.Context, orgs []domain.ClubOrg) error DeleteClubWithOrgs(ctx context.Context, clubID int) error UpdateClub(ctx context.Context, c *domain.Club, o []domain.ClubOrg) error + AddClubPhotos(ctx context.Context, p []domain.ClubPhoto) error + DeleteClubPhoto(ctx context.Context, ids int) error + GetPhotoClubID(ctx context.Context, photoID int) (int, error) + UpdateClubPhotos(ctx context.Context, clubID int, photos []domain.ClubPhoto) error } type ClubService struct { @@ -47,14 +52,22 @@ func (s *ClubService) GetClub(ctx context.Context, id int) (*responses.GetClub, mainOrgs, err := s.storage.GetClubOrgs(ctx, id) if err != nil { - err = fmt.Errorf("can't storage.GetClubOrgs: %w", err) - return nil, err + if err == postgres.ErrPostgresNotFoundError { + mainOrgs = []domain.ClubOrg{} + } else { + err = fmt.Errorf("can't storage.GetClubOrgs: %w", err) + return nil, err + } } subOrgs, err := s.storage.GetClubSubOrgs(ctx, id) if err != nil { - err = fmt.Errorf("can't storage.GetClubSubOrgs: %w", err) - return nil, err + if err == postgres.ErrPostgresNotFoundError { + subOrgs = []domain.ClubOrg{} + } else { + err = fmt.Errorf("can't storage.GetClubSubOrgs: %w", err) + return nil, err + } } ids := make([]int, 0, len(mainOrgs)+len(subOrgs)+1) @@ -72,7 +85,7 @@ func (s *ClubService) GetClub(ctx context.Context, id int) (*responses.GetClub, return nil, err } - return mapper.MakeResponseClub(club, &mainOrgs, &subOrgs, &ims) + return mapper.MakeResponseClub(club, mainOrgs, subOrgs, ims) } func (s *ClubService) GetClubsByName(ctx context.Context, name string) (*responses.GetClubsByName, error) { @@ -82,10 +95,6 @@ func (s *ClubService) GetClubsByName(ctx context.Context, name string) (*respons return nil, err } - if len(res) == 0 { - return nil, fmt.Errorf("no club found") - } - ids := make([]int, 0, len(res)) for _, club := range res { ids = append(ids, club.LogoId) @@ -114,10 +123,6 @@ func (s *ClubService) GetClubsByType(ctx context.Context, type_ string) (*respon return nil, err } - if len(res) == 0 { - return nil, fmt.Errorf("no club found") - } - ids := make([]int, 0, len(res)) for _, club := range res { ids = append(ids, club.LogoId) @@ -147,10 +152,6 @@ func (s *ClubService) GetAllClubs(ctx context.Context) (*responses.GetAllClubs, return nil, err } - if len(res) == 0 { - return nil, fmt.Errorf("no club found") - } - ids := make([]int, 0, len(res)) for _, club := range res { ids = append(ids, club.LogoId) @@ -173,12 +174,22 @@ func (s *ClubService) GetAllClubs(ctx context.Context) (*responses.GetAllClubs, func (s *ClubService) GetClubMembers(ctx context.Context, clubID int) (*responses.GetClubMembers, error) { orgs, err := s.storage.GetClubOrgs(ctx, clubID) if err != nil { - return nil, fmt.Errorf("can't storage.GetClubOrgs: %w", err) + if err == postgres.ErrPostgresNotFoundError { + orgs = []domain.ClubOrg{} + } else { + err = fmt.Errorf("can't storage.GetClubOrgs: %w", err) + return nil, err + } } subOrgs, err := s.storage.GetClubSubOrgs(ctx, clubID) if err != nil { - return nil, fmt.Errorf("can't storage.GetClubSubOrgs: %w", err) + if err == postgres.ErrPostgresNotFoundError { + subOrgs = []domain.ClubOrg{} + } else { + err = fmt.Errorf("can't storage.GetClubSubOrgs: %w", err) + return nil, err + } } if len(orgs)+len(subOrgs) == 0 { @@ -247,12 +258,13 @@ func (s *ClubService) UpdateClub(ctx context.Context, req *requests.UpdateClub) func (s *ClubService) GetClubMediaFiles(ctx context.Context, clubID int) (*responses.GetClubMedia, error) { clubPhotos, err := s.storage.GetClubMediaFiles(ctx, clubID) if err != nil { - return nil, fmt.Errorf("can't storage.GetClubMediaFiles: %w", err) + if err == postgres.ErrPostgresNotFoundError { + clubPhotos = []domain.ClubPhoto{} + } else { + return nil, fmt.Errorf("can't storage.GetClubMediaFiles: %w", err) + } } - if len(clubPhotos) == 0 { - return nil, fmt.Errorf("no club photo found") - } ids := make([]int, 0, len(clubPhotos)) for _, photo := range clubPhotos { ids = append(ids, photo.MediaID) @@ -264,3 +276,64 @@ func (s *ClubService) GetClubMediaFiles(ctx context.Context, clubID int) (*respo return mapper.MakeResponseClubMediaFiles(clubID, clubPhotos, media) } + +func (s *ClubService) PostClubPhoto(ctx context.Context, req *requests.PostClubPhoto) error { + photos := make([]domain.ClubPhoto, 0, len(req.Photos)) + for _, p := range req.Photos { + photos = append(photos, domain.ClubPhoto{ + ClubID: req.ClubID, + MediaID: p.MediaID, + RefNumber: p.RefNumber, + ID: 0, + }) + } + + err := s.storage.AddClubPhotos(ctx, photos) + if err != nil { + return fmt.Errorf("can't storage.AddClubPhotos: %w", err) + } + return nil +} + +func (s *ClubService) DeleteClubPhoto(ctx context.Context, req *requests.DeleteClubPhoto) error { + clubID, err := s.storage.GetPhotoClubID(ctx, req.PhotoID) + if err != nil { + if err == postgres.ErrPostgresNotFoundError { + return fmt.Errorf("photo not found") + } + return fmt.Errorf("can't storage.GetPhotoClubID: %w", err) + } + if clubID != req.ClubID { + return fmt.Errorf("photo is not from the specified club") + } + err = s.storage.DeleteClubPhoto(ctx, req.PhotoID) + if err != nil { + return fmt.Errorf("can't storage.DeleteClubPhoto: %w", err) + } + return nil +} + +func (s *ClubService) UpdateClubPhoto(ctx context.Context, req *requests.UpdateClubPhoto) error { + clubID := req.ClubID + photos := make([]domain.ClubPhoto, 0, len(req.Photos)) + for _, p := range req.Photos { + photos = append(photos, domain.ClubPhoto{ + ClubID: clubID, + MediaID: p.MediaID, + RefNumber: p.RefNumber, + }) + } + err := s.storage.UpdateClubPhotos(ctx, clubID, photos) + if err != nil { + return fmt.Errorf("can't storage.UpdateClubPhotos: %w", err) + } + return err + +} + +func (s *ClubService) GetClearancePost(ctx context.Context, resp *responses.CheckResponse) (*responses.GetClearance, error) { + if resp.IsAdmin { + return &responses.GetClearance{Access: true, Comment: ""}, nil + } + return &responses.GetClearance{Access: false, Comment: "only admins"}, nil +} diff --git a/internal/app/documents.go b/internal/app/documents.go index 5703e50..c658b11 100644 --- a/internal/app/documents.go +++ b/internal/app/documents.go @@ -63,7 +63,7 @@ func (s *DocumentsService) GetDocumentsByClubID(ctx context.Context, clubID int) } func (s *DocumentsService) PostDocument(ctx context.Context, doc *requests.PostDocument) (*responses.PostDocument, error) { - var key = fmt.Sprintf("%d/%s", doc.ClubID, doc.Name) + var key = fmt.Sprintf("%d/%s", doc.CategoryID, doc.Name) err := s.storage.PostDocument(ctx, doc.Name, key, doc.Data, doc.ClubID, doc.CategoryID) if err != nil { @@ -83,7 +83,7 @@ func (s *DocumentsService) DeleteDocument(ctx context.Context, id int) error { } func (s *DocumentsService) UpdateDocument(ctx context.Context, doc *requests.UpdateDocument) (*responses.UpdateDocument, error) { - var key = fmt.Sprintf("%d/%s", doc.ClubID, doc.Name) + var key = fmt.Sprintf("%d/%s", doc.CategoryID, doc.Name) err := s.storage.UpdateDocument(ctx, doc.ID, doc.Name, key, doc.Data, doc.ClubID, doc.CategoryID) if err != nil { diff --git a/internal/app/events.go b/internal/app/events.go index 6b1373d..ce4d336 100644 --- a/internal/app/events.go +++ b/internal/app/events.go @@ -84,6 +84,11 @@ func (s *EventsService) GetEventsByRange(ctx context.Context, from, to time.Time return mapper.MakeResponseEventsByRange(res, eventMediaFiles) } +// func (s *EventsService) GetEventMemberRoles(ctx context.Context, id int) (*responses.GetEventMemberRoles, error) { + +// return mapper.MakeResponseEventMemberRoles(res) +// } + func (s *EventsService) PostEvent(ctx context.Context, event *domain.Event) error { err := s.storage.PostEvent(ctx, event) if err != nil { diff --git a/internal/app/mapper/club.go b/internal/app/mapper/club.go index 70298c8..612673b 100644 --- a/internal/app/mapper/club.go +++ b/internal/app/mapper/club.go @@ -8,8 +8,8 @@ import ( "github.com/STUD-IT-team/bmstu-stud-web-backend/internal/domain/responses" ) -func MakeMainOrg(org *domain.ClubOrg, images *map[int]domain.MediaFile) (*responses.MainOrg, error) { - if _, ok := (*images)[org.MediaID]; !ok { +func MakeMainOrg(org *domain.ClubOrg, images map[int]domain.MediaFile) (*responses.MainOrg, error) { + if _, ok := images[org.MediaID]; !ok { return &responses.MainOrg{}, fmt.Errorf("can't find image for org id: %v", org.MediaID) } @@ -20,12 +20,12 @@ func MakeMainOrg(org *domain.ClubOrg, images *map[int]domain.MediaFile) (*respon TgUrl: org.Telegram, Spec: org.RoleSpec, RoleName: org.RoleName, - Image: (*images)[org.MediaID], + Image: images[org.MediaID], }, nil } -func MakeSubOrg(org *domain.ClubOrg, images *map[int]domain.MediaFile) (*responses.SubClubOrg, error) { - if _, ok := (*images)[org.MediaID]; !ok { +func MakeSubOrg(org *domain.ClubOrg, images map[int]domain.MediaFile) (*responses.SubClubOrg, error) { + if _, ok := images[org.MediaID]; !ok { return &responses.SubClubOrg{}, fmt.Errorf("can't find image for org id: %v", org.MediaID) } @@ -36,13 +36,13 @@ func MakeSubOrg(org *domain.ClubOrg, images *map[int]domain.MediaFile) (*respons VkUrl: org.Vk, TgUrl: org.Telegram, Spec: org.RoleSpec, - Image: (*images)[org.MediaID], + Image: images[org.MediaID], }, nil } -func MakeResponseClub(club *domain.Club, mainOrgs *[]domain.ClubOrg, subOrgs *[]domain.ClubOrg, images *map[int]domain.MediaFile) (*responses.GetClub, error) { - if _, ok := (*images)[club.LogoId]; !ok { +func MakeResponseClub(club *domain.Club, mainOrgs []domain.ClubOrg, subOrgs []domain.ClubOrg, images map[int]domain.MediaFile) (*responses.GetClub, error) { + if _, ok := images[club.LogoId]; !ok { return &responses.GetClub{}, fmt.Errorf("can't get logo media for club id: %v", club.LogoId) } @@ -50,14 +50,16 @@ func MakeResponseClub(club *domain.Club, mainOrgs *[]domain.ClubOrg, subOrgs *[] ID: club.ID, Name: club.Name, ShortName: club.ShortName, - Logo: (*images)[club.LogoId], + Logo: images[club.LogoId], Description: club.Description, Type: club.Type, VkUrl: club.VkUrl, TgUrl: club.TgUrl, + MainOrgs: make([]responses.MainOrg, 0), + SubOrgs: make([]responses.SubClubOrg, 0), } - for _, org := range *mainOrgs { + for _, org := range mainOrgs { m, err := MakeMainOrg(&org, images) if err != nil { return nil, err @@ -65,7 +67,7 @@ func MakeResponseClub(club *domain.Club, mainOrgs *[]domain.ClubOrg, subOrgs *[] r.MainOrgs = append(r.MainOrgs, *m) } - for _, org := range *subOrgs { + for _, org := range subOrgs { s, err := MakeSubOrg(&org, images) if err != nil { return nil, err @@ -89,6 +91,12 @@ func MakeResponseAllClub(clubs []domain.Club, logos map[int]domain.MediaFile, or orgMap[org.ClubID] = append(orgMap[org.ClubID], o) } + for _, club := range clubs { + if _, ok := orgMap[club.ID]; !ok { + orgMap[club.ID] = []responses.ClubOrg{} + } + } + for _, club := range clubs { if _, ok := logos[club.LogoId]; !ok { return nil, fmt.Errorf("can't find logo for club id %v", logos) @@ -111,11 +119,13 @@ func MakeResponseAllClub(clubs []domain.Club, logos map[int]domain.MediaFile, or func MakeResponseClubMembers(clubID int, mainOrgs []domain.ClubOrg, subOrgs []domain.ClubOrg, images map[int]domain.MediaFile) (*responses.GetClubMembers, error) { r := &responses.GetClubMembers{ - ID: clubID, + ID: clubID, + MainOrgs: []responses.MainOrg{}, + SubOrgs: []responses.SubClubOrg{}, } for _, org := range mainOrgs { - m, err := MakeMainOrg(&org, &images) + m, err := MakeMainOrg(&org, images) if err != nil { return nil, err } @@ -123,7 +133,7 @@ func MakeResponseClubMembers(clubID int, mainOrgs []domain.ClubOrg, subOrgs []do } for _, org := range subOrgs { - s, err := MakeSubOrg(&org, &images) + s, err := MakeSubOrg(&org, images) if err != nil { return nil, err } @@ -160,7 +170,10 @@ func ParsePostClub(req *requests.PostClub) (*domain.Club, []domain.ClubOrg, erro } func MakeResponseClubMediaFiles(clubID int, clubPhotos []domain.ClubPhoto, media map[int]domain.MediaFile) (*responses.GetClubMedia, error) { - r := &responses.GetClubMedia{ID: clubID} + r := &responses.GetClubMedia{ + ID: clubID, + Media: []responses.ClubMedia{}, + } for _, f := range clubPhotos { media, ok := media[f.MediaID] if !ok { diff --git a/internal/app/mapper/dcoument-categories.go b/internal/app/mapper/document-categories.go similarity index 100% rename from internal/app/mapper/dcoument-categories.go rename to internal/app/mapper/document-categories.go diff --git a/internal/app/media.go b/internal/app/media.go index ee3afbf..fc87ae9 100644 --- a/internal/app/media.go +++ b/internal/app/media.go @@ -40,7 +40,7 @@ func (s *MediaService) PostMedia(ctx context.Context, req *requests.PostMedia) ( return &responses.PostMedia{}, fmt.Errorf("can't storage.PutMedia: %v", err) } - return mapper.MakeResponsePostMedia(id), err + return mapper.MakeResponsePostMedia(id), nil } const bcryptCost = 12 @@ -56,7 +56,7 @@ func (s *MediaService) PostMediaBcrypt(ctx context.Context, req *requests.PostMe return &responses.PostMedia{}, fmt.Errorf("can't storage.PutMedia: %v", err) } - return mapper.MakeResponsePostMedia(id), fmt.Errorf("can't storage.UploadObject: %v", err) + return mapper.MakeResponsePostMedia(id), nil } func (s *MediaService) ClearUpMedia(ctx context.Context, logger *logrus.Logger) error { diff --git a/internal/domain/event-member-role.go b/internal/domain/event-member-role.go new file mode 100644 index 0000000..d0703af --- /dev/null +++ b/internal/domain/event-member-role.go @@ -0,0 +1,6 @@ +package domain + +type EventMemberRole struct { + ID int `json:"id"` + Name string `json:"name"` +} diff --git a/internal/domain/member.go b/internal/domain/member.go index 5e9c02e..a1ee798 100644 --- a/internal/domain/member.go +++ b/internal/domain/member.go @@ -9,6 +9,5 @@ type Member struct { Telegram string `json:"telegram"` Vk string `json:"vk"` Name string `json:"name"` - RoleID int `json:"role_id"` - IsAdmin bool `json:"isAdmin"` + IsAdmin bool `json:"is_admin"` } diff --git a/internal/domain/requests/delete-club-photo.go b/internal/domain/requests/delete-club-photo.go new file mode 100644 index 0000000..9b7ad2e --- /dev/null +++ b/internal/domain/requests/delete-club-photo.go @@ -0,0 +1,63 @@ +package requests + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/STUD-IT-team/bmstu-stud-web-backend/internal/domain" + "github.com/go-chi/chi" +) + +type DeleteClubPhoto struct { + PhotoID int `json:"photo_id"` + ClubID int `json:"club_id"` +} + +type DeleteClubPhotoPointer struct { + ClubID int `json:"club_id"` + PhotoID *int `json:"photo_id"` +} + +func (c *DeleteClubPhoto) Bind(req *http.Request) error { + clubID, err := strconv.Atoi(chi.URLParam(req, "club_id")) + if err != nil { + return fmt.Errorf("can't Atoi on club_id in request: %w", err) + } + + pc := DeleteClubPhotoPointer{} + + decoder := json.NewDecoder(req.Body) + decoder.DisallowUnknownFields() + err = decoder.Decode(&pc) + if err != nil { + return fmt.Errorf("can't json decoder on DeleteClubPhoto: %v", err) + } + if decoder.More() { + return fmt.Errorf("extraneous data after JSON object on DeleteClubPhoto") + } + + err = pc.validate() + if err != nil { + return fmt.Errorf("%v: %v", domain.ErrIncorrectRequest, err) + } + + c.ClubID = clubID + c.PhotoID = *pc.PhotoID + return c.validate() +} + +func (c *DeleteClubPhoto) validate() error { + if c.ClubID == 0 { + return fmt.Errorf("require: id") + } + return nil +} + +func (pc *DeleteClubPhotoPointer) validate() error { + if pc.PhotoID == nil { + return fmt.Errorf("require: photo_id") + } + return nil +} diff --git a/internal/domain/requests/post-club-photo.go b/internal/domain/requests/post-club-photo.go new file mode 100644 index 0000000..b3cc148 --- /dev/null +++ b/internal/domain/requests/post-club-photo.go @@ -0,0 +1,67 @@ +package requests + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/STUD-IT-team/bmstu-stud-web-backend/internal/domain" + "github.com/go-chi/chi" +) + +type PostClubPhoto struct { + ClubID int `json:"club_id"` + Photos []ClubPhoto `json:"photos"` +} + +type PostClubPhotoPointer struct { + ClubID int `json:"club_id"` + Photos []ClubPhoto `json:"photos"` +} + +type ClubPhoto struct { + MediaID int `json:"media_id"` + RefNumber int `json:"ref_number"` +} + +func (c *PostClubPhoto) Bind(req *http.Request) error { + id, err := strconv.Atoi(chi.URLParam(req, "club_id")) + if err != nil { + return fmt.Errorf("can't Atoi on club_id in request: %w", err) + } + pc := PostClubPhotoPointer{} + + decoder := json.NewDecoder(req.Body) + decoder.DisallowUnknownFields() + err = decoder.Decode(&pc) + + if err != nil { + return fmt.Errorf("can't json decoder on PostClub: %v", err) + } + + if decoder.More() { + return fmt.Errorf("postClub Bind: extraneous data after JSON object") + } + + err = pc.validate() + if err != nil { + return fmt.Errorf("%v: %v", domain.ErrIncorrectRequest, err) + } + *c = PostClubPhoto{ + ClubID: id, + Photos: pc.Photos, + } + return c.validate() +} + +func (c *PostClubPhoto) validate() error { + return nil +} + +func (pc *PostClubPhotoPointer) validate() error { + if pc.Photos == nil { + return fmt.Errorf("require: photos") + } + return nil +} diff --git a/internal/domain/requests/post-club.go b/internal/domain/requests/post-club.go index 322880f..0bad638 100644 --- a/internal/domain/requests/post-club.go +++ b/internal/domain/requests/post-club.go @@ -21,15 +21,15 @@ type PostClub struct { } type PostClubPointer struct { - Name *string `json:"name"` - ShortName *string `json:"short_name"` - Description *string `json:"description"` - Type *string `json:"type"` - LogoId *int `json:"logo_id"` - VkUrl *string `json:"vk_url"` - TgUrl *string `json:"tg_url"` - ParentID *int `json:"parent_id"` - Orgs *[]ClubOrg `json:"orgs"` + Name *string `json:"name"` + ShortName *string `json:"short_name"` + Description *string `json:"description"` + Type *string `json:"type"` + LogoId *int `json:"logo_id"` + VkUrl *string `json:"vk_url"` + TgUrl *string `json:"tg_url"` + ParentID *int `json:"parent_id"` + Orgs []ClubOrg `json:"orgs"` } type ClubOrg struct { MemberID int `json:"member_id"` @@ -66,7 +66,7 @@ func (c *PostClub) Bind(req *http.Request) error { VkUrl: *pc.VkUrl, TgUrl: *pc.TgUrl, ParentID: *pc.ParentID, - Orgs: *pc.Orgs, + Orgs: pc.Orgs, } return c.validate() diff --git a/internal/domain/requests/update-club-photo.go b/internal/domain/requests/update-club-photo.go new file mode 100644 index 0000000..7e85999 --- /dev/null +++ b/internal/domain/requests/update-club-photo.go @@ -0,0 +1,48 @@ +package requests + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/STUD-IT-team/bmstu-stud-web-backend/internal/domain" + "github.com/go-chi/chi" +) + +type UpdateClubPhoto struct { + PostClubPhoto +} + +type UpdateClubPhotoPointer struct { + PostClubPhotoPointer +} + +func (c *UpdateClubPhoto) Bind(req *http.Request) error { + id, err := strconv.Atoi(chi.URLParam(req, "club_id")) + if err != nil { + return fmt.Errorf("can't Atoi on club_id in request: %w", err) + } + pc := PostClubPhotoPointer{} + + decoder := json.NewDecoder(req.Body) + decoder.DisallowUnknownFields() + err = decoder.Decode(&pc) + + if err != nil { + return fmt.Errorf("can't json decoder on updateClubPhoto: %v", err) + } + + if decoder.More() { + return fmt.Errorf("updateClubPhoto Bind: extraneous data after JSON object") + } + + err = pc.validate() + if err != nil { + return fmt.Errorf("%v: %v", domain.ErrIncorrectRequest, err) + } + c.ClubID = id + c.Photos = pc.Photos + + return c.validate() +} diff --git a/internal/domain/requests/update-club.go b/internal/domain/requests/update-club.go index 66e193e..b15e98d 100644 --- a/internal/domain/requests/update-club.go +++ b/internal/domain/requests/update-club.go @@ -55,7 +55,7 @@ func (p *UpdateClub) Bind(req *http.Request) error { VkUrl: *pf.VkUrl, TgUrl: *pf.TgUrl, ParentID: *pf.ParentID, - Orgs: *pf.Orgs, + Orgs: pf.Orgs, }, } diff --git a/internal/domain/requests/update-member.go b/internal/domain/requests/update-member.go index 29856ed..d8daae8 100644 --- a/internal/domain/requests/update-member.go +++ b/internal/domain/requests/update-member.go @@ -17,7 +17,7 @@ type UpdateMember struct { Telegram string `json:"telegram"` Vk string `json:"vk"` Name string `json:"name"` - IsAdmin bool `json:"isAdmin"` + IsAdmin bool `json:"is_admin"` } type UpdateMemberPointer struct { @@ -26,7 +26,7 @@ type UpdateMemberPointer struct { Telegram *string `json:"telegram"` Vk *string `json:"vk"` Name *string `json:"name"` - IsAdmin *bool `json:"isAdmin"` + IsAdmin *bool `json:"is_admin"` } func (f *UpdateMember) Bind(req *http.Request) error { diff --git a/internal/domain/responses/get-all-event-member-roles.go b/internal/domain/responses/get-all-event-member-roles.go new file mode 100644 index 0000000..ad9a5c0 --- /dev/null +++ b/internal/domain/responses/get-all-event-member-roles.go @@ -0,0 +1,10 @@ +package responses + +type GetAllEventMemberRoles struct { + EventMemberRole []EventMemberRole `json:"roles"` +} + +type EventMemberRole struct { + ID int `json:"id"` + Name string `json:"name"` +} diff --git a/internal/domain/responses/get-all-events.go b/internal/domain/responses/get-all-events.go index 58023af..a6d1edf 100644 --- a/internal/domain/responses/get-all-events.go +++ b/internal/domain/responses/get-all-events.go @@ -7,7 +7,7 @@ import ( ) type GetAllEvents struct { - Event []Event `json:"event"` + Event []Event `json:"events"` } type Event struct { diff --git a/internal/domain/responses/get-clearance.go b/internal/domain/responses/get-clearance.go new file mode 100644 index 0000000..cf1d4bf --- /dev/null +++ b/internal/domain/responses/get-clearance.go @@ -0,0 +1,6 @@ +package responses + +type GetClearance struct { + Access bool `json:"access"` + Comment string `json:"comment"` +} diff --git a/internal/domain/responses/get-member.go b/internal/domain/responses/get-member.go index 9f1993f..f2788c5 100644 --- a/internal/domain/responses/get-member.go +++ b/internal/domain/responses/get-member.go @@ -9,5 +9,5 @@ type GetMember struct { Telegram string `json:"telegram"` Vk string `json:"vk"` Name string `json:"name"` - IsAdmin bool `json:"isAdmin"` + IsAdmin bool `json:"is_admin"` } diff --git a/internal/infrastructure/postgres/club.go b/internal/infrastructure/postgres/club.go index 826ef28..2814f9d 100644 --- a/internal/infrastructure/postgres/club.go +++ b/internal/infrastructure/postgres/club.go @@ -182,8 +182,8 @@ func (s *Postgres) GetClubsByType(_ context.Context, type_ string) ([]domain.Clu const getClubOrgs = ` SELECT - role_name, - role_spec, + orgs.role_name, + orgs.role_spec, mem.id, mem.hash_password, mem.login, @@ -191,7 +191,6 @@ SELECT mem.telegram, mem.vk, mem.name, - mem.role_id, mem.is_admin, clubs.name as club_name FROM club_org @@ -205,7 +204,6 @@ JOIN telegram, vk, name, - role_id, is_admin FROM member ) mem @@ -218,6 +216,16 @@ JOIN FROM club ) as clubs ON (club_org.club_id = clubs.id) +JOIN +( + SELECT + id, + role_name, + role_spec + FROM club_role + WHERE role_clearance = 2 +) as orgs +ON orgs.id = club_org.role_id WHERE club_id = $1 AND club_id > 0 ` @@ -239,7 +247,6 @@ func (s *Postgres) GetClubOrgs(_ context.Context, clubID int) ([]domain.ClubOrg, &c.Telegram, &c.Vk, &c.Name, - &c.RoleID, &c.IsAdmin, &c.ClubName, ) @@ -256,8 +263,8 @@ func (s *Postgres) GetClubOrgs(_ context.Context, clubID int) ([]domain.ClubOrg, const getClubsOrgs = ` SELECT - role_name, - role_spec, + orgs.role_name, + orgs.role_spec, mem.id, mem.hash_password, mem.login, @@ -265,7 +272,6 @@ SELECT mem.telegram, mem.vk, mem.name, - mem.role_id, mem.is_admin, clubs.name as club_name, clubs.id as club_id @@ -293,6 +299,16 @@ JOIN FROM club ) as clubs ON (club_org.club_id = clubs.id) +JOIN +( + SELECT + id, + role_name, + role_spec + FROM club_role + WHERE role_clearance = 2 +) as orgs +ON orgs.id = club_org.role_id WHERE club_id = ANY($1) AND club_id > 0 ` @@ -314,7 +330,6 @@ func (s *Postgres) GetClubsOrgs(_ context.Context, clubIDs []int) ([]domain.Club &c.Telegram, &c.Vk, &c.Name, - &c.RoleID, &c.IsAdmin, &c.ClubName, &c.ClubID, @@ -332,8 +347,8 @@ func (s *Postgres) GetClubsOrgs(_ context.Context, clubIDs []int) ([]domain.Club const getClubSubOrgs = ` SELECT - role_name, - role_spec, + orgs.role_name, + orgs.role_spec, mem.id, mem.hash_password, mem.login, @@ -341,7 +356,6 @@ SELECT mem.telegram, mem.vk, mem.name, - mem.role_id, mem.is_admin, clubs.name as club_name FROM club_org @@ -355,7 +369,6 @@ JOIN telegram, vk, name, - role_id, is_admin FROM member ) mem @@ -369,6 +382,16 @@ JOIN WHERE id > 0 ) as clubs ON (club_org.club_id = clubs.id) +JOIN +( + SELECT + id, + role_name, + role_spec + FROM club_role + WHERE role_clearance = 2 +) as orgs +ON orgs.id = club_org.role_id WHERE club_id = ANY((SELECT id FROM club WHERE parent_id = $1)) AND club_id > 0 ` @@ -390,7 +413,6 @@ func (s *Postgres) GetClubSubOrgs(_ context.Context, clubID int) ([]domain.ClubO &c.Telegram, &c.Vk, &c.Name, - &c.RoleID, &c.IsAdmin, &c.ClubName, ) @@ -408,8 +430,8 @@ func (s *Postgres) GetClubSubOrgs(_ context.Context, clubID int) ([]domain.ClubO const getAllClubOrgs = ` SELECT - role_name, - role_spec, + orgs.role_name, + orgs.role_spec, member_id, club_id, mem.hash_password, @@ -418,7 +440,6 @@ SELECT mem.telegram, mem.vk, mem.name, - mem.role_id, mem.is_admin, clubs.name as club_name FROM club_org @@ -432,7 +453,6 @@ JOIN telegram, vk, name, - role_id, is_admin FROM member ) mem @@ -446,6 +466,16 @@ JOIN WHERE id > 0 ) as clubs ON (club_org.club_id = clubs.id) +JOIN +( + SELECT + id, + role_name, + role_spec + FROM club_role + WHERE role_clearance = 2 +) as orgs +ON orgs.id = club_org.role_id ` func (s *Postgres) GetAllClubOrgs(_ context.Context) ([]domain.ClubOrg, error) { @@ -467,7 +497,6 @@ func (s *Postgres) GetAllClubOrgs(_ context.Context) ([]domain.ClubOrg, error) { &c.Telegram, &c.Vk, &c.Name, - &c.RoleID, &c.IsAdmin, &c.ClubName, ) @@ -516,13 +545,21 @@ func (s *Postgres) AddClub(_ context.Context, c *domain.Club) (int, error) { return id, nil } +const addOrgRole = ` +INSERT INTO club_role ( + role_name, + role_spec, + role_clearance +) VALUES ($1, $2, 2) +RETURNING id +` + const addOrgs = ` INSERT INTO club_org ( - role_name, - role_spec, + role_id, member_id, club_id -) VALUES ($1, $2, $3, $4) +) VALUES ($1, $2, $3) ` func (s *Postgres) AddOrgs(_ context.Context, orgs []domain.ClubOrg) error { @@ -532,7 +569,13 @@ func (s *Postgres) AddOrgs(_ context.Context, orgs []domain.ClubOrg) error { } for _, org := range orgs { - _, err = tx.Exec(addOrgs, org.RoleName, org.RoleSpec, org.ID, org.ClubID) + var id int + err := s.db.QueryRow(addOrgRole, org.RoleName, org.RoleSpec).Scan(&id) + if err != nil { + tx.Rollback() + return wrapPostgresError(err) + } + _, err = tx.Exec(addOrgs, id, org.ID, org.ClubID) if err != nil { tx.Rollback() return wrapPostgresError(err) @@ -575,7 +618,8 @@ func (s *Postgres) GetClubMediaFiles(clubID int) ([]domain.ClubPhoto, error) { } const deleteClub = "DELETE FROM club WHERE id = $1" -const deleteClubOrgs = "DELETE FROM club_org WHERE club_id = $1" +const deleteClubOrgs = "DELETE FROM club_org using club_role WHERE club_id = $1 and club_role.id = club_org.role_id and club_role.role_clearance = 2" +const deleteClubMembers = "DELETE FROM club_org WHERE club_id = $1" const deleteClubPhotos = "DELETE FROM club_photo WHERE club_id = $1" const deleteClubEncounters = "DELETE FROM encounter WHERE club_id = $1" const deleteClubDocuments = "DELETE FROM document WHERE club_id = $1" @@ -587,7 +631,7 @@ func (s *Postgres) DeleteClubWithOrgs(_ context.Context, clubID int) error { return wrapPostgresError(err) } - _, err = tx.Exec(deleteClubOrgs, clubID) + _, err = tx.Exec(deleteClubMembers, clubID) if err != nil { tx.Rollback() return wrapPostgresError(err) @@ -675,7 +719,13 @@ func (s *Postgres) UpdateClub(_ context.Context, c *domain.Club, o []domain.Club } for _, org := range o { - _, err = tx.Exec(addOrgs, org.RoleName, org.RoleSpec, org.ID, c.ID) + var id int + err := s.db.QueryRow(addOrgRole, org.RoleName, org.RoleSpec).Scan(&id) + if err != nil { + tx.Rollback() + return wrapPostgresError(err) + } + _, err = tx.Exec(addOrgs, id, org.ID, org.ClubID) if err != nil { tx.Rollback() return wrapPostgresError(err) @@ -684,3 +734,104 @@ func (s *Postgres) UpdateClub(_ context.Context, c *domain.Club, o []domain.Club err = tx.Commit() return wrapPostgresError(err) } + +const addClubPhoto = "INSERT INTO club_photo (ref_num, club_id, media_id) VALUES ($1, $2, $3)" + +func (s *Postgres) AddClubPhotos(_ context.Context, p []domain.ClubPhoto) error { + tx, err := s.db.Begin() + if err != nil { + return wrapPostgresError(err) + } + for _, photo := range p { + _, err = tx.Exec(addClubPhoto, photo.RefNumber, photo.ClubID, photo.MediaID) + if err != nil { + tx.Rollback() + return wrapPostgresError(err) + } + } + return wrapPostgresError(tx.Commit()) +} + +const getClubPhoto = "SELECT id, ref_num, club_id, media_id FROM club_photo WHERE club_id = $1" +const upsertClubPhoto = ` +INSERT INTO club_photo (ref_num, club_id, media_id) VALUES ($1, $2, $3) +ON CONFLICT (media_id, club_id) DO UPDATE SET ref_num=$1 +` + +func (s *Postgres) UpdateClubPhotos(_ context.Context, clubID int, p []domain.ClubPhoto) error { + tx, err := s.db.Begin() + if err != nil { + return wrapPostgresError(err) + } + + dbPhotos := []domain.ClubPhoto{} + rows, err := tx.Query(getClubPhoto, p[0].ClubID) + if err != nil { + tx.Rollback() + return wrapPostgresError(err) + } + for rows.Next() { + dbPhoto := domain.ClubPhoto{} + err := rows.Scan(&dbPhoto.ID, &dbPhoto.RefNumber, &dbPhoto.ClubID, &dbPhoto.MediaID) + if err != nil { + tx.Rollback() + return wrapPostgresError(err) + } + dbPhotos = append(dbPhotos, dbPhoto) + } + + toDelete := make([]int, 0, len(dbPhotos)) + + for _, dbPhoto := range dbPhotos { + found := false + for _, photo := range p { + if dbPhoto.MediaID == photo.MediaID { + found = true + break + } + } + if !found { + toDelete = append(toDelete, dbPhoto.ID) + } + } + + for _, id := range toDelete { + _, err := tx.Exec(deleteClubPhoto, id) + if err != nil { + tx.Rollback() + return wrapPostgresError(err) + } + } + + for _, photo := range p { + _, err := tx.Exec(upsertClubPhoto, photo.RefNumber, clubID, photo.MediaID) + if err != nil { + tx.Rollback() + return wrapPostgresError(err) + } + } + return wrapPostgresError(tx.Commit()) +} + +const deleteClubPhoto = "DELETE FROM club_photo WHERE id = $1" + +func (s *Postgres) DeleteClubPhoto(_ context.Context, id int) error { + tx, err := s.db.Begin() + if err != nil { + return wrapPostgresError(err) + } + _, err = tx.Exec(deleteClubPhoto, id) + if err != nil { + tx.Rollback() + return wrapPostgresError(err) + } + return wrapPostgresError(tx.Commit()) +} + +const getPhotoClubID = "SELECT club_id FROM club_photo WHERE id = $1" + +func (s *Postgres) GetPhotoClubID(_ context.Context, photoID int) (int, error) { + var clubID int + err := s.db.QueryRow(getPhotoClubID, photoID).Scan(&clubID) + return clubID, wrapPostgresError(err) +} diff --git a/internal/infrastructure/postgres/guard.go b/internal/infrastructure/postgres/guard.go index 7a34541..7c0c7f0 100644 --- a/internal/infrastructure/postgres/guard.go +++ b/internal/infrastructure/postgres/guard.go @@ -16,3 +16,14 @@ func (p *Postgres) AddMember(ctx context.Context, mem *domain.Member) (int, erro } return id, nil } + +const getMemberHash = `SELECT hash_password FROM member WHERE login = $1` + +func (p *Postgres) GetMemberHash(_ context.Context, login string) (string, error) { + var hash string + err := p.db.QueryRow(getMemberHash, login).Scan(&hash) + if err != nil { + return "", wrapPostgresError(err) + } + return hash, nil +} diff --git a/internal/infrastructure/postgres/members.go b/internal/infrastructure/postgres/members.go index 33960dc..9c9325e 100644 --- a/internal/infrastructure/postgres/members.go +++ b/internal/infrastructure/postgres/members.go @@ -6,7 +6,7 @@ import ( "github.com/STUD-IT-team/bmstu-stud-web-backend/internal/domain" ) -const getAllMembersQuery = "SELECT id, hash_password, login, media_id, telegram, vk, name, role_id, is_admin FROM member" +const getAllMembersQuery = "SELECT id, login, media_id, telegram, vk, name, is_admin FROM member" func (p *Postgres) GetAllMembers(_ context.Context) ([]domain.Member, error) { var members []domain.Member @@ -21,13 +21,11 @@ func (p *Postgres) GetAllMembers(_ context.Context) ([]domain.Member, error) { err = rows.Scan( &member.ID, - &member.HashPassword, &member.Login, &member.MediaID, &member.Telegram, &member.Vk, &member.Name, - &member.RoleID, &member.IsAdmin, ) @@ -45,7 +43,7 @@ func (p *Postgres) GetAllMembers(_ context.Context) ([]domain.Member, error) { return members, nil } -const getMemberQuery = "SELECT id, hash_password, login, media_id, telegram, vk, name, role_id, is_admin FROM member WHERE id=$1" +const getMemberQuery = "SELECT id, login, media_id, telegram, vk, name, is_admin FROM member WHERE id=$1" func (p *Postgres) GetMember(ctx context.Context, id int) (*domain.Member, error) { var member domain.Member @@ -55,13 +53,11 @@ func (p *Postgres) GetMember(ctx context.Context, id int) (*domain.Member, error id, ).Scan( &member.ID, - &member.HashPassword, &member.Login, &member.MediaID, &member.Telegram, &member.Vk, &member.Name, - &member.RoleID, &member.IsAdmin, ) @@ -72,7 +68,7 @@ func (p *Postgres) GetMember(ctx context.Context, id int) (*domain.Member, error return &member, nil } -const getMembersByNameQuery = "SELECT id, hash_password, login, media_id, telegram, vk, name, role_id, is_admin FROM member WHERE name ILIKE $1" +const getMembersByNameQuery = "SELECT id, login, media_id, telegram, vk, name, is_admin FROM member WHERE name ILIKE $1" func (p *Postgres) GetMembersByName(_ context.Context, name string) ([]domain.Member, error) { var members []domain.Member @@ -87,13 +83,11 @@ func (p *Postgres) GetMembersByName(_ context.Context, name string) ([]domain.Me err = rows.Scan( &member.ID, - &member.HashPassword, &member.Login, &member.MediaID, &member.Telegram, &member.Vk, &member.Name, - &member.RoleID, &member.IsAdmin, ) @@ -112,19 +106,17 @@ func (p *Postgres) GetMembersByName(_ context.Context, name string) ([]domain.Me } const postMemberQuery = `INSERT INTO member - (hash_password, login, media_id, telegram, vk, name, role_id, is_admin) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` + (login, media_id, telegram, vk, name, is_admin) + VALUES ($1, $2, $3, $4, $5, $6)` func (p *Postgres) PostMember(ctx context.Context, member *domain.Member) error { _, err := p.db.Exec( postMemberQuery, - member.HashPassword, member.Login, member.MediaID, member.Telegram, member.Vk, member.Name, - member.RoleID, member.IsAdmin, ) @@ -154,26 +146,22 @@ func (p *Postgres) DeleteMember(ctx context.Context, id int) error { const updateMemberQuery = ` UPDATE member SET -hash_password=$1, -login=$2, -media_id=$3, -telegram=$4, -vk=$5, -name=$6, -role_id=$7, -is_admin=$8 -WHERE id=$9` +login=$1, +media_id=$2, +telegram=$3, +vk=$4, +name=$5, +is_admin=$6 +WHERE id=$7` func (p *Postgres) UpdateMember(ctx context.Context, member *domain.Member) error { tag, err := p.db.Exec( updateMemberQuery, - member.HashPassword, member.Login, member.MediaID, member.Telegram, member.Vk, member.Name, - member.RoleID, member.IsAdmin, member.ID, ) @@ -187,12 +175,12 @@ func (p *Postgres) UpdateMember(ctx context.Context, member *domain.Member) erro return nil } -const getMemberByLoginQuery = "SELECT id, login, hash_password FROM member WHERE login=$1;" +const getMemberByLoginQuery = "SELECT id, login, hash_password, media_id, telegram, vk, name, is_admin FROM member WHERE login=$1;" func (p *Postgres) GetMemberByLogin(_ context.Context, login string) (*domain.Member, error) { var user domain.Member - err := p.db.QueryRow(getMemberByLoginQuery, login).Scan(&user.ID, &user.Login, &user.HashPassword) + err := p.db.QueryRow(getMemberByLoginQuery, login).Scan(&user.ID, &user.Login, &user.HashPassword, &user.MediaID, &user.Telegram, &user.Vk, &user.Name, &user.IsAdmin) if err != nil { return nil, wrapPostgresError(err) } diff --git a/internal/ports/clubs.go b/internal/ports/clubs.go index 8af3638..2900dee 100644 --- a/internal/ports/clubs.go +++ b/internal/ports/clubs.go @@ -46,10 +46,40 @@ func (h *ClubsHandler) Routes() chi.Router { r.Post("/", h.r.Wrap(h.PostClub)) r.Delete("/{club_id}", h.r.Wrap(h.DeleteClub)) r.Put("/{club_id}", h.r.Wrap(h.UpdateClub)) + r.Post("/media/{club_id}", h.r.Wrap(h.PostClubMedia)) + r.Delete("/media/{club_id}", h.r.Wrap(h.DeleteClubMedia)) + r.Put("/media/{club_id}", h.r.Wrap(h.UpdateClubMedia)) + r.Get("/clearance/post/", h.r.Wrap(h.GetClearancePost)) return r } +func (h *ClubsHandler) GetClearancePost(w http.ResponseWriter, req *http.Request) handler.Response { + h.logger.Info("ClubsHandler: got PostClub request") + + access, err := getAccessToken(req) + if err != nil { + h.logger.Warnf("can't get access token: %v", err) + return handler.UnauthorizedResponse() + } + + resp, err := h.guard.Check(context.Background(), &requests.CheckRequest{AccessToken: access}) + if err != nil || !resp.Valid { + h.logger.Warnf("Unauthorized request: %v", err) + return handler.UnauthorizedResponse() + } + + h.logger.Infof("ClubsHandler: GetClearancePost Authenticated: %v", resp.MemberID) + + response, err := h.clubs.GetClearancePost(context.Background(), resp) + if err != nil { + h.logger.Warnf("can't clubs.GetClearancePost GetClearancePost: %v", err) + return handler.InternalServerErrorResponse() + } + + return handler.OkResponse(response) +} + // GetAllClubs // // @Summary Возвращает все клубы из БД @@ -458,3 +488,168 @@ func (h *ClubsHandler) UpdateClub(w http.ResponseWriter, req *http.Request) hand return handler.OkResponse(nil) } + +// PostClubPhoto +// +// @Summary Добавляет в клуб фотографии клуба в базу данных +// @Description Добавляет в клуб фотографии клуба в базу данных +// @Tags auth.club +// @Produce json +// @Param club_id path int true "club id" +// @Param request body requests.PostClubPhoto true "post club photo data" +// @Success 200 +// @Failure 400 +// @Failure 401 +// @Failure 409 +// @Failure 500 +// @Router /clubs/media/{club_id} [post] +// @Security Authorized +func (h *ClubsHandler) PostClubMedia(w http.ResponseWriter, req *http.Request) handler.Response { + h.logger.Info("ClubsHandler: got PostClubMedia request") + + access, err := getAccessToken(req) + if err != nil { + h.logger.Warnf("can't get access token: %v", err) + return handler.UnauthorizedResponse() + } + + resp, err := h.guard.Check(context.Background(), &requests.CheckRequest{AccessToken: access}) + if err != nil || !resp.Valid { + h.logger.Warnf("Unauthorized request: %v", err) + return handler.UnauthorizedResponse() + } + + h.logger.Infof("ClubsHandler: PostClubMedia Authenticated: %v", resp.MemberID) + + photo := &requests.PostClubPhoto{} + err = photo.Bind(req) + if err != nil { + h.logger.Warnf("can't service.PostClubMedia %v", err) + return handler.BadRequestResponse() + } + + h.logger.Infof("ClubsHandler: parse request.") + + err = h.clubs.PostClubPhoto(context.Background(), photo) + if err != nil { + h.logger.Warnf("can't service.PostClubMedia %v", err) + if errors.Is(err, postgres.ErrPostgresUniqueConstraintViolation) { + return handler.ConflictResponse() + } else if errors.Is(err, postgres.ErrPostgresForeignKeyViolation) { + return handler.BadRequestResponse() + } + return handler.InternalServerErrorResponse() + } + + return handler.OkResponse(nil) +} + +// DeleteClubPhoto +// +// @Summary Удаляет фотографию из фотографий клуба +// @Description Удаляет фотографию из фотографий клуба +// @Tags auth.club +// @Produce json +// @Param club_id path int true "club id" +// @Param request body requests.DeleteClubPhoto true "post club photo data" +// @Success 200 +// @Failure 400 +// @Failure 401 +// @Failure 404 +// @Failure 500 +// @Router /clubs/media/{club_id} [delete] +// @Security Authorized +func (h *ClubsHandler) DeleteClubMedia(w http.ResponseWriter, req *http.Request) handler.Response { + h.logger.Info("ClubsHandler: got DeleteClubMedia request") + + access, err := getAccessToken(req) + if err != nil { + h.logger.Warnf("can't get access token: %v", err) + return handler.UnauthorizedResponse() + } + + resp, err := h.guard.Check(context.Background(), &requests.CheckRequest{AccessToken: access}) + if err != nil || !resp.Valid { + h.logger.Warnf("Unauthorized request: %v", err) + return handler.UnauthorizedResponse() + } + + h.logger.Infof("ClubsHandler: DeleteClubMedia Authenticated: %v", resp.MemberID) + + photo := &requests.DeleteClubPhoto{} + err = photo.Bind(req) + if err != nil { + h.logger.Warnf("can't parse DeleteClubMedia %v", err) + return handler.BadRequestResponse() + } + h.logger.Infof("ClubsHandler: parse request.") + + err = h.clubs.DeleteClubPhoto(context.Background(), photo) + if err != nil { + h.logger.Warnf("can't service.DeleteClubMedia %v", err) + if errors.Is(err, postgres.ErrPostgresForeignKeyViolation) { + return handler.BadRequestResponse() + } else if errors.Is(err, postgres.ErrPostgresNotFoundError) { + return handler.NotFoundResponse() + } + return handler.InternalServerErrorResponse() + } + + return handler.OkResponse(nil) +} + +// UpdateClubPhoto +// +// @Summary Обновляет все фотографии клуба +// @Description Обновляет все фотографии клуба +// @Tags auth.club +// @Produce json +// @Param club_id path int true "club id" +// @Param request body requests.UpdateClubPhoto true "post club photo data" +// @Success 200 +// @Failure 400 +// @Failure 401 +// @Failure 409 +// @Failure 500 +// @Router /clubs/media/{club_id} [put] +// @Security Authorized +func (h *ClubsHandler) UpdateClubMedia(w http.ResponseWriter, req *http.Request) handler.Response { + h.logger.Info("ClubsHandler: got UpdateClubMedia request") + + access, err := getAccessToken(req) + if err != nil { + h.logger.Warnf("can't get access token: %v", err) + return handler.UnauthorizedResponse() + } + + resp, err := h.guard.Check(context.Background(), &requests.CheckRequest{AccessToken: access}) + if err != nil || !resp.Valid { + h.logger.Warnf("Unauthorized request: %v", err) + return handler.UnauthorizedResponse() + } + + h.logger.Infof("ClubsHandler: UpdateClubMedia Authenticated: %v", resp.MemberID) + + photo := &requests.UpdateClubPhoto{} + + err = photo.Bind(req) + + if err != nil { + h.logger.Warnf("can't parse UpdateClubMedia %v", err) + return handler.BadRequestResponse() + } + h.logger.Infof("ClubsHandler: parse request.") + + err = h.clubs.UpdateClubPhoto(context.Background(), photo) + if err != nil { + h.logger.Warnf("can't service.UpdateClubMedia %v", err) + if errors.Is(err, postgres.ErrPostgresForeignKeyViolation) { + return handler.BadRequestResponse() + } else if errors.Is(err, postgres.ErrPostgresNotFoundError) { + return handler.NotFoundResponse() + } + return handler.InternalServerErrorResponse() + } + + return handler.OkResponse(nil) +} diff --git a/internal/ports/events.go b/internal/ports/events.go index 89ed179..7779b9e 100644 --- a/internal/ports/events.go +++ b/internal/ports/events.go @@ -42,9 +42,16 @@ func (h *EventsHandler) Routes() chi.Router { r.Get("/", h.r.Wrap(h.GetAllEvents)) r.Get("/{id}", h.r.Wrap(h.GetEvent)) r.Get("/range/", h.r.Wrap(h.GetEventsByRange)) + // r.Get("/members/roles/", h.r.Wrap(h.GetEventMemberRoles)) + // r.Get("/members/", h.r.Wrap(h.GetAllEventMembers)) + // r.Get("/members/search_by_event/{event_id}", h.r.Wrap(h.GetEventMembersByEvent)) + // r.Get("/members/{member_id}", h.r.Wrap(h.GetEventMember)) r.Post("/", h.r.Wrap(h.PostEvent)) r.Delete("/{id}", h.r.Wrap(h.DeleteEvent)) r.Put("/{id}", h.r.Wrap(h.UpdateEvent)) + // r.Post("/members/", h.r.Wrap(h.PostEventMember)) + // r.Delete("/members/{id}", h.r.Wrap(h.DeleteEventMember)) + // r.Put("/members/{id}", h.r.Wrap(h.UpdateEventMember)) return r } @@ -161,6 +168,52 @@ func (h *EventsHandler) GetEventsByRange(w http.ResponseWriter, req *http.Reques return handler.OkResponse(res) } +// // GetEventMemberRoles get all roles for members of events +// // +// // @Summary Retrieve all event roles +// // @Description Get a list of all roles for members of events +// // @Tags auth.events +// // @Produce json +// // @Success 200 {object} responses.GetEventMemberRoles +// // @Failure 404 +// // @Failure 401 +// // @Failure 500 +// // @Router /events/members/roles/ [get] +// // @Security Authorised +// func (h *EventsHandler) GetEventMemberRoles(w http.ResponseWriter, req *http.Request) handler.Response { +// h.logger.Info("EventsHandler: got GetEventMemberRoles request") + +// accessToken, err := getAccessToken(req) +// if err != nil { +// h.logger.Warnf("can't get access token GetEventMemberRoles: %v", err) +// return handler.UnauthorizedResponse() +// } + +// resp, err := h.guard.Check(context.Background(), &requests.CheckRequest{AccessToken: accessToken}) +// if err != nil || !resp.Valid { +// h.logger.Warnf("can't GuardService.Check on GetEventMemberRoles: %v", err) +// return handler.UnauthorizedResponse() +// } + +// h.logger.Infof("EventsHandler: GetEventMemberRoles Authenticated: %v", resp.MemberID) + +// res, err := h.events.GetEventMemberRoles(context.Background(), resp.MemberID) +// if err != nil { +// h.logger.Warnf("can't EventsService.GetEventMemberRoles: %v", err) +// if errors.Is(err, postgres.ErrPostgresNotFoundError) { +// return handler.NotFoundResponse() +// } else if errors.Is(err, app.ErrInvalidCredentials) { +// return handler.UnauthorizedResponse() +// } else { +// return handler.InternalServerErrorResponse() +// } +// } + +// h.logger.Info("EventsHandler: request GetEventMemberRoles done") + +// return handler.OkResponse(res) +// } + // PostEvent creates a new event item // // @Summary Create a new event item @@ -185,7 +238,7 @@ func (h *EventsHandler) PostEvent(w http.ResponseWriter, req *http.Request) hand resp, err := h.guard.Check(context.Background(), &requests.CheckRequest{AccessToken: accessToken}) if err != nil || !resp.Valid { - h.logger.Warnf("can't GuardService.Check on DeleteEvent: %v", err) + h.logger.Warnf("can't GuardService.Check on PostEvent: %v", err) return handler.UnauthorizedResponse() } @@ -299,7 +352,7 @@ func (h *EventsHandler) UpdateEvent(w http.ResponseWriter, req *http.Request) ha resp, err := h.guard.Check(context.Background(), &requests.CheckRequest{AccessToken: accessToken}) if err != nil || !resp.Valid { - h.logger.Warnf("can't GuardService.Check on DeleteEvent: %v", err) + h.logger.Warnf("can't GuardService.Check on UpdateEvent: %v", err) return handler.UnauthorizedResponse() } diff --git a/internal/ports/guard.go b/internal/ports/guard.go index a5b4200..e1a39b0 100644 --- a/internal/ports/guard.go +++ b/internal/ports/guard.go @@ -36,9 +36,9 @@ func (h *GuardHandler) BasePrefix() string { func (h *GuardHandler) Routes() chi.Router { r := chi.NewRouter() - r.Post("/login", h.r.Wrap(h.LoginUser)) - r.Post("/logout", h.r.Wrap(h.LogoutUser)) - r.Post("/register", h.r.Wrap(h.RegisterUser)) + r.Post("/login/", h.r.Wrap(h.LoginUser)) + r.Post("/logout/", h.r.Wrap(h.LogoutUser)) + r.Post("/register/", h.r.Wrap(h.RegisterUser)) return r } diff --git a/internal/ports/media.go b/internal/ports/media.go index 80ea305..fabfc0d 100644 --- a/internal/ports/media.go +++ b/internal/ports/media.go @@ -38,11 +38,11 @@ func (h *MediaHandler) BasePrefix() string { func (h *MediaHandler) Routes() chi.Router { r := chi.NewRouter() - r.Post("/public", h.r.Wrap(h.PostMediaPublic)) - r.Post("/private", h.r.Wrap(h.PostMediaPrivate)) - r.Get("/default", h.r.Wrap(h.GetMediaDefault)) + r.Post("/public/", h.r.Wrap(h.PostMediaPublic)) + r.Post("/private/", h.r.Wrap(h.PostMediaPrivate)) + r.Get("/default/", h.r.Wrap(h.GetMediaDefault)) r.Get("/default/{id}", h.r.Wrap(h.GetMediaDefaultByID)) - r.Post("/default", h.r.Wrap(h.PostMediaDefault)) + r.Post("/default/", h.r.Wrap(h.PostMediaDefault)) r.Delete("/default/{id}", h.r.Wrap(h.DeleteMediaDefault)) r.Put("/default/{id}", h.r.Wrap(h.UpdateMediaDefault)) diff --git a/internal/storage/club.go b/internal/storage/club.go index 912a2e2..9b2617e 100644 --- a/internal/storage/club.go +++ b/internal/storage/club.go @@ -20,6 +20,10 @@ type clubStorage interface { AddOrgs(ctx context.Context, orgs []domain.ClubOrg) error DeleteClubWithOrgs(ctx context.Context, clubID int) error UpdateClub(ctx context.Context, c *domain.Club, o []domain.ClubOrg) error + AddClubPhotos(ctx context.Context, p []domain.ClubPhoto) error + DeleteClubPhoto(ctx context.Context, id int) error + GetPhotoClubID(ctx context.Context, photoID int) (int, error) + UpdateClubPhotos(ctx context.Context, clubID int, photos []domain.ClubPhoto) error } func (s *storage) GetClub(ctx context.Context, id int) (*domain.Club, error) { @@ -73,3 +77,19 @@ func (s *storage) DeleteClubWithOrgs(ctx context.Context, clubID int) error { func (s *storage) UpdateClub(ctx context.Context, c *domain.Club, o []domain.ClubOrg) error { return s.postgres.UpdateClub(ctx, c, o) } + +func (s *storage) AddClubPhotos(ctx context.Context, p []domain.ClubPhoto) error { + return s.postgres.AddClubPhotos(ctx, p) +} + +func (s *storage) DeleteClubPhoto(ctx context.Context, id int) error { + return s.postgres.DeleteClubPhoto(ctx, id) +} + +func (s *storage) GetPhotoClubID(ctx context.Context, photoID int) (int, error) { + return s.postgres.GetPhotoClubID(ctx, photoID) +} + +func (s *storage) UpdateClubPhotos(ctx context.Context, clubID int, photos []domain.ClubPhoto) error { + return s.postgres.UpdateClubPhotos(ctx, clubID, photos) +} diff --git a/internal/storage/guard.go b/internal/storage/guard.go index 3b95f3a..1e8deb9 100644 --- a/internal/storage/guard.go +++ b/internal/storage/guard.go @@ -2,6 +2,7 @@ package storage import ( "context" + "fmt" "time" "github.com/STUD-IT-team/bmstu-stud-web-backend/internal/domain" @@ -36,7 +37,7 @@ func (s *storage) GetMemberAndValidatePassword(ctx context.Context, login string err = hasher.CompareHashAndPassword(user.HashPassword, []byte(password)) if err != nil { - return domain.Member{}, err + return domain.Member{}, fmt.Errorf("%w, size: %v", err, len(user.HashPassword)) } return *user, nil diff --git a/migrations/001_init_database.sql b/migrations/001_init_database.sql index c187db6..e9fa1f8 100644 --- a/migrations/001_init_database.sql +++ b/migrations/001_init_database.sql @@ -1,5 +1,6 @@ -- +goose Up -- +goose StatementBegin + create table IF NOT EXISTS member ( id serial primary key, @@ -9,17 +10,9 @@ create table IF NOT EXISTS member telegram text default '', vk text default '', name text default '', - role_id int default 0, is_admin boolean default false ); -create table IF NOT EXISTS member_role -( - role_id serial primary key, - role_name text not null, - role_spec text not null -); - CREATE table IF NOT EXISTS feed ( id serial primary key, @@ -38,41 +31,21 @@ CREATE table IF NOT EXISTS feed create table if not exists mediafile ( id serial primary key, name text default '', - image text default '' -); - -create table IF NOT EXISTS event -( - id serial primary key, - title text not null, - description text not null, - prompt text not null, - media_id int not null, - date timestamp not null, - approved boolean default false, - created_at timestamp not null, - created_by int not null, - reg_url text default '', - reg_open_date timestamp not null, - feedback_url text default '' + key text not null unique ); alter table member add foreign key (media_id) references mediafile(id); --- alter table member add foreign key (role_id) references member_role(role_id); alter table feed add foreign key (media_id) references mediafile(id); alter table feed add foreign key (created_by) references member(id); -alter table event add foreign key (media_id) references mediafile(id); -alter table event add foreign key (created_by) references member(id); - -- +goose StatementEnd -- +goose Down -- +goose StatementBegin -drop table IF EXISTS member_role CASCADE; -drop table IF EXISTS member CASCADE; -drop table IF EXISTS feed CASCADE; -drop table IF EXISTS event CASCADE; + drop table IF EXISTS mediafile CASCADE; --- +goose StatementEnd +drop table IF EXISTS feed CASCADE; +drop table IF EXISTS member CASCADE; + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/002_init_clubs.sql b/migrations/002_init_clubs.sql new file mode 100644 index 0000000..f6d2113 --- /dev/null +++ b/migrations/002_init_clubs.sql @@ -0,0 +1,96 @@ +-- +goose Up +-- +goose StatementBegin + +create TABLE if not exists encounter ( + id serial primary KEY, + count text default '', + description text default '', + club_id int not null +); + +create TABLE IF NOT EXISTS event +( + id serial primary KEY, + title text not null, + description text not null, + prompt text not null, + media_id int not null, + date timestamp not null, + approved boolean default false, + created_at timestamp not null, + club_id int not null, + main_org int not null, + reg_url text default '', + reg_open_date timestamp not null, + feedback_url text default '' +); + +create TABLE if not exists club ( + id serial primary KEY, + name text default '' unique, + short_name text default '', + description text default '', + type text default '', + logo int not null, + vk_url text default '', + tg_url text default '', + parent_id int default null +); + +create TABLE IF NOT EXISTS club_role +( + id serial primary KEY, + role_name text not null, + role_spec text not null, + role_clearance int default 0 +); +-- 0 - гость +-- 1 - участник +-- 2 - руководитель +-- 3 - админ + +-- TODO in domain +create TABLE if not exists club_photo ( + id serial primary KEY, + ref_num int default 0, + media_id int not null, + club_id int not null +); + +create TABLE if not exists club_org ( + id serial primary KEY, + club_id int not null, + member_id int not null, + role_id int not null +); + +ALTER TABLE encounter ADD FOREIGN KEY (club_id) REFERENCES club(id); + +ALTER TABLE club ADD FOREIGN KEY (logo) REFERENCES mediafile(id); +ALTER TABLE club ADD FOREIGN KEY (parent_id) REFERENCES club(id); + +ALTER TABLE event ADD FOREIGN KEY (club_id) REFERENCES club(id); +ALTER TABLE event ADD FOREIGN KEY (media_id) REFERENCES mediafile(id); +ALTER TABLE event ADD FOREIGN KEY (main_org) REFERENCES member(id); + +ALTER TABLE club_photo ADD FOREIGN KEY (media_id) REFERENCES mediafile(id); +ALTER TABLE club_photo ADD FOREIGN KEY (club_id) REFERENCES club(id); +ALTER TABLE club_photo ADD CONSTRAINT media_club_ids_unique UNIQUE (club_id, media_id); + +ALTER TABLE club_org ADD FOREIGN KEY (club_id) REFERENCES club(id); +ALTER TABLE club_org ADD FOREIGN KEY (member_id) REFERENCES member(id); +ALTER TABLE club_org ADD FOREIGN KEY (role_id) REFERENCES club_role(id); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +DROP TABLE IF EXISTS club_role CASCADE; +DROP TABLE IF EXISTS club_org CASCADE; +DROP TABLE IF EXISTS club_photo CASCADE; +DROP TABLE IF EXISTS event CASCADE; +DROP TABLE IF EXISTS encounter CASCADE; +DROP TABLE IF EXISTS club CASCADE; + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/002_new_structures_sql.sql b/migrations/002_new_structures_sql.sql deleted file mode 100644 index fc887f5..0000000 --- a/migrations/002_new_structures_sql.sql +++ /dev/null @@ -1,56 +0,0 @@ --- +goose Up --- +goose StatementBegin - -create table if not exists encounter ( - id serial primary key, - count text default '', - description text default '', - club_id int not null -); - -create table if not exists club ( - id serial primary key, - name text default '' unique, - short_name text default '', - description text default '', - type text default '', - logo int not null, - vk_url text default '', - tg_url text default '' -); - --- TODO in domain -create table if not exists club_photo ( - id serial primary key, - ref_num int default 0, - media_id int not null, - club_id int not null -); - -create table if not exists club_org ( - id serial primary key, - club_id int not null, - member_id int not null, - role_name text default '', - role_spec text default '' -); - -alter table encounter add foreign key (club_id) references club(id); - -alter table club add FOREIGN KEY (logo) REFERENCES mediafile(id); - -alter table club_photo add FOREIGN KEY (media_id) REFERENCES mediafile(id); -alter table club_photo add FOREIGN KEY (club_id) REFERENCES club(id); - -alter table club_org add FOREIGN KEY (club_id) REFERENCES club(id); -alter table club_org add FOREIGN KEY (member_id) REFERENCES member(id); - --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -drop table if exists encounter CASCADE; -drop table if exists club_photo CASCADE; -drop table if exists club_org CASCADE; -drop table if exists club CASCADE; --- +goose StatementEnd diff --git a/migrations/011_init_documents_table.sql b/migrations/003_init_documents.sql similarity index 90% rename from migrations/011_init_documents_table.sql rename to migrations/003_init_documents.sql index 79933bc..e7e1ba7 100644 --- a/migrations/011_init_documents_table.sql +++ b/migrations/003_init_documents.sql @@ -1,5 +1,6 @@ -- +goose Up -- +goose StatementBegin + create table IF NOT EXISTS document ( id serial primary key, @@ -22,6 +23,8 @@ alter table document add foreign key (club_id) references club(id); -- +goose Down -- +goose StatementBegin + +drop table IF EXISTS category CASCADE; drop table IF EXISTS document CASCADE; -drop table IF EXISTS category CASCADE; --- +goose StatementEnd + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/003_insert_club_test_data.sql b/migrations/003_insert_club_test_data.sql deleted file mode 100644 index 6441d91..0000000 --- a/migrations/003_insert_club_test_data.sql +++ /dev/null @@ -1,34 +0,0 @@ --- +goose Up --- +goose StatementBegin - -INSERT INTO mediafile (id, name, image) -VALUES -(0, 'default', 'default'); -- Important, do not remove - -INSERT INTO mediafile (name, image) -VALUES -('IT-dep.jpg', 'image1.jpg'), -('NIT-dep.jpg', 'image2.jpg'), -('Finance-dep.jpg', 'image3.jpg'); - -INSERT INTO club (id, logo) VALUES (0, 0); -- Important, do not remove -INSERT INTO club (name, short_name, description, type, logo, vk_url, tg_url) -VALUES -('IT-department', 'IT-dep', 'Typo iT', 'IT', 1, 'vk.com', 'tg.me'), -('Not IT-department', 'NIT-dep', 'Typo Ne iT', 'IT', 1, 'vk.com', 'tg.me'), -('Finance-department', 'Finance-dep', 'Typo Fin', 'Finance', 2, 'vk.com', 'tg.me'), -('HR-department', 'HR-dep', 'Typo Hr', 'HR', 2, 'vk.com', 'tg.me'), -('Marketing-department', 'Marketing-dep', 'Typo Mark', 'Marketing' , 3, 'vk.com', 'tg.me'), -('Sales-department', 'Sales-dep', 'Typo Sa', 'Sales', 3, 'vk.com', 'tg.me'), -('Engineering-department', 'Engineering-dep', 'Typo Eng', 'Engineering', 3, 'vk.com', 'tg.me'); - - --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin - -DELETE FROM club; -DELETE FROM mediafile; - --- +goose StatementEnd \ No newline at end of file diff --git a/migrations/013_init_video_table.sql b/migrations/004_init_main_video.sql similarity index 94% rename from migrations/013_init_video_table.sql rename to migrations/004_init_main_video.sql index 149b7b8..d55bf3e 100644 --- a/migrations/013_init_video_table.sql +++ b/migrations/004_init_main_video.sql @@ -1,5 +1,6 @@ -- +goose Up -- +goose StatementBegin + create table IF NOT EXISTS main_video ( id serial PRIMARY KEY, @@ -15,5 +16,7 @@ create table IF NOT EXISTS main_video -- +goose Down -- +goose StatementBegin + drop table IF EXISTS main_video CASCADE; --- +goose StatementEnd + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/004_insert_feed_test_data.sql b/migrations/004_insert_feed_test_data.sql deleted file mode 100644 index 3e10b7c..0000000 --- a/migrations/004_insert_feed_test_data.sql +++ /dev/null @@ -1,40 +0,0 @@ --- +goose Up --- +goose StatementBegin - -INSERT INTO member_role (role_name, role_spec) VALUES -('admin', 'admin'), -('user', 'user'); - -INSERT INTO member (hash_password, login, media_id, role_id) -VALUES -('1234', '1234', 1, 1), -('1', '1', 2, 2); - -INSERT INTO feed (title, approved, description, media_id, vk_post_url, updated_at, created_at, views, created_by) -VALUES -('11', true, '33', 1, '132', '2004-10-19 10:23:54', '2004-10-19 10:23:54', 13, 1), -('1', true, '33', 2, '132', '2004-10-19 10:23:54', '2004-10-19 10:23:54', 13, 1), -('22', false, '44', 3, '321', '2005-11-19 10:23:54', '1900-10-19 10:23:54', 14, 2); - -INSERT INTO encounter (count, description, club_id) -VALUES -('1', 'kcoc', 0), -('11', 'kcoc', 0), -('2', 'sllab', 1), -('22', 'sllab', 2); - -INSERT INTO event (title, description, prompt, media_id, date, approved, created_at, created_by, reg_url, reg_open_date, feedback_url) -VALUES -('kcoc', 'sllab', 'kcid', 1, '2005-11-19 10:23:54', true, '2005-11-19 10:23:54', 1, 'ahh', '2005-11-19 10:23:54', '123'), -('sinep', 'stun', 'nibor', 1, '2005-11-19 10:23:54', true, '2005-11-19 10:23:54', 2, 'ahh', '2005-11-19 10:23:54', '123'); - --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DELETE FROM encounter; -DELETE FROM feed; -DELETE FROM event; -DELETE FROM member; -DELETE FROM member_role; --- +goose StatementEnd \ No newline at end of file diff --git a/migrations/005_added_tree_structure_to_clubs.sql b/migrations/005_added_tree_structure_to_clubs.sql deleted file mode 100644 index 452ead4..0000000 --- a/migrations/005_added_tree_structure_to_clubs.sql +++ /dev/null @@ -1,14 +0,0 @@ --- +goose Up --- +goose StatementBegin - -ALTER TABLE club ADD COLUMN parent_id int default null; -alter table club add FOREIGN KEY (parent_id) REFERENCES club(id); - --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin - -ALTER TABLE club DROP COLUMN parent_id; - --- +goose StatementEnd \ No newline at end of file diff --git a/migrations/014_init_default_media_table.sql b/migrations/005_init_default_media.sql similarity index 79% rename from migrations/014_init_default_media_table.sql rename to migrations/005_init_default_media.sql index edb19f2..9ddd31b 100644 --- a/migrations/014_init_default_media_table.sql +++ b/migrations/005_init_default_media.sql @@ -1,5 +1,6 @@ -- +goose Up -- +goose StatementBegin + create table IF NOT EXISTS default_media ( id serial PRIMARY KEY, @@ -11,5 +12,7 @@ create table IF NOT EXISTS default_media -- +goose Down -- +goose StatementBegin -drop table IF EXISTS default_media; --- +goose StatementEnd + +drop table IF EXISTS default_media CASCADE; + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/006_add_orgs_to_clubs.sql b/migrations/006_add_orgs_to_clubs.sql deleted file mode 100644 index 429e0d6..0000000 --- a/migrations/006_add_orgs_to_clubs.sql +++ /dev/null @@ -1,36 +0,0 @@ --- +goose Up --- +goose StatementBegin - -INSERT INTO member (hash_password, login, media_id, telegram, vk, name, role_id, is_admin) -VALUES -('test', 'toha', 1, '@toha', 'vk.com/toha', 'Антон Павленко', 1, true), -('$2a$10$kj0GwI3q1H0PgOzuLqK5uOhPPvA42upL8CdIm/4luikQBYNKVxXay', 'imp', 1, '@imp', 'vk.com/imp', 'Дмитрий Шахнович', 1, true), -('test', 'dasha', 1, '@dasha', 'vk.com/dasha', 'Дарья Серышева', 1, true), -('test', 'paioid', 1, '@paioid', 'vk.com/paioid', 'Андрей Поляков', 1, true), -('test', 'admin', 1, '@admin', 'vk.com/admin', 'Админ', 1, true), -('test', 'user', 1, '@user', 'vk.com/user', 'Юзер', 1, true), -('test', 'user2', 1, '@user2', 'vk.com/user2', 'Юзер2', 1, true); - - -INSERT INTO club_org (club_id, member_id, role_name, role_spec) -VALUES -(1, 1, 'Молодец', 'IT'), -(1, 1, 'Красава', 'NIT'), -(1, 2, 'Веселый', 'Finance'), -(1, 2, 'Хорошо', 'HR'), -(1, 3, 'Богатый', 'Marketing'), -(1, 3, 'Молодец', 'Sales'), -(2, 4, 'Умный', 'Engineering'); - -UPDATE club -SET parent_id = 1 -WHERE id = 2; - --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin - -DELETE FROM club_org; - --- +goose StatementEnd \ No newline at end of file diff --git a/migrations/006_init_event_member.sql b/migrations/006_init_event_member.sql new file mode 100644 index 0000000..57dcf7e --- /dev/null +++ b/migrations/006_init_event_member.sql @@ -0,0 +1,31 @@ +-- +goose Up +-- +goose StatementBegin + +create table IF NOT EXISTS event_member +( + id serial primary key, + event_id int not null, + member_id int not null, + role_id int not null, + division text default '' +); + +create table IF NOT EXISTS event_member_role +( + id serial primary key, + name text not null +); + +alter table event_member add foreign key (event_id) references event(id); +alter table event_member add foreign key (member_id) references member(id); +alter table event_member add foreign key (role_id) references event_member_role(id); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +drop table IF EXISTS event_member_role CASCADE; +drop table IF EXISTS event_member CASCADE; + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/007_add_club_photo_for_test.sql b/migrations/007_add_club_photo_for_test.sql deleted file mode 100644 index cd19850..0000000 --- a/migrations/007_add_club_photo_for_test.sql +++ /dev/null @@ -1,32 +0,0 @@ --- +goose Up --- +goose StatementBegin - -INSERT INTO mediafile (name, image) -VALUES -('4.jpg', 'image4.jpg'), -('5.jpg', 'image5.jpg'), -('6.jpg', 'image6.jpg'), -('7.jpg', 'image7.jpg'), -('8.jpg', 'image8.jpg'), -('9.jpg', 'image9.jpg'); - -INSERT INTO club_photo (club_id, media_id, ref_num) -VALUES -(1, 1, 1), -(1, 1, 2), -(2, 1, 1), -(2, 1, 2), -(3, 1, 1), -(3, 1, 2), -(3, 1, 3), -(3, 1, 4), -(3, 1, 5); - --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin - -DELETE FROM club_photo; - --- +goose StatementEnd \ No newline at end of file diff --git a/migrations/008_alter_media_tbl.sql b/migrations/008_alter_media_tbl.sql deleted file mode 100644 index 48e6111..0000000 --- a/migrations/008_alter_media_tbl.sql +++ /dev/null @@ -1,13 +0,0 @@ --- +goose Up --- +goose StatementBegin -ALTER table mediafile rename column image to image_url; - -ALTER table mediafile ALTER column image_url type TEXT; -update mediafile set image_url = 'localhost:5000/about/arch.png'; - --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin - --- +goose StatementEnd diff --git a/migrations/008_insert_media.sql b/migrations/008_insert_media.sql new file mode 100644 index 0000000..cecff60 --- /dev/null +++ b/migrations/008_insert_media.sql @@ -0,0 +1,28 @@ +-- +goose Up +-- +goose StatementBegin + +insert into mediafile (name, key) +values +('1.jpg', '1.jpg'), +('2.jpg', '2.jpg'), +('3.jpg', '3.jpg'), +('4.jpg', '4.jpg'), +('5.jpg', '5.jpg'), +('6.jpg', '6.jpg'), +('7.jpg', '7.jpg'), +('8.jpg', '8.jpg'), +('9.jpg', '9.jpg'), +('10.jpg', '10.jpg'), +('11.jpg', '11.jpg'); + +insert into default_media (media_id) +VALUES (1), (2), (3); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +truncate table mediafile CASCADE; + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/009_insert_mediafile_test_data.sql b/migrations/009_insert_mediafile_test_data.sql deleted file mode 100644 index 8a9ba2c..0000000 --- a/migrations/009_insert_mediafile_test_data.sql +++ /dev/null @@ -1,26 +0,0 @@ --- +goose Up --- +goose StatementBegin - -ALTER TABLE mediafile RENAME column image_url TO key; - -UPDATE mediafile set key = 'arch.png' where id=1; -UPDATE mediafile set key = '2.jpg' where id=2; -UPDATE mediafile set key = '3.jpg' where id=3; -UPDATE mediafile set key = '4.jpg' where id=4; -UPDATE mediafile set key = '5.jpg' where id=5; -UPDATE mediafile set key = '6.jpg' where id=6; -UPDATE mediafile set key = '7.jpg' where id=7; -UPDATE mediafile set key = '8.jpg' where id=8; -UPDATE mediafile set key = '9.jpg' where id=9; -INSERT INTO mediafile (key, name) VALUES -('10.jpg', 'image10.jpg'), -('11.jpg', 'image11.jpg'), -('12.jpg', 'image12.jpg'); - -ALTER TABLE mediafile ADD CONSTRAINT key_unique UNIQUE (key) --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin - --- +goose StatementEnd \ No newline at end of file diff --git a/migrations/009_insert_member.sql b/migrations/009_insert_member.sql new file mode 100644 index 0000000..916a834 --- /dev/null +++ b/migrations/009_insert_member.sql @@ -0,0 +1,19 @@ +-- +goose Up +-- +goose StatementBegin + +insert into member (hash_password, login, media_id, telegram, vk, name, is_admin) +values +('$2y$10$GvW6IPLIAda8K7cIVeQ02.Sh/s5hPi2QHXZRSxkwG0kZiG00qOyQi', 'TestHeadMaster', 1, '@testHeadTelegram', 'TestHeadTelegram', 'Антон Успенский', true), +('$2y$10$GvW6IPLIAda8K7cIVeQ02.Sh/s5hPi2QHXZRSxkwG0kZiG00qOyQi', 'TestTPmaster', 2, '@TestTPmaster', 'TestTPmasterTelegram', 'Максим Демьянов', false), +('$2y$10$GvW6IPLIAda8K7cIVeQ02.Sh/s5hPi2QHXZRSxkwG0kZiG00qOyQi', 'TestHeadMissis', 3, '@TestHeadMissis', 'TestHeadMissisTelegram', 'Екатерина Донскова', false), +('$2y$10$GvW6IPLIAda8K7cIVeQ02.Sh/s5hPi2QHXZRSxkwG0kZiG00qOyQi', '@testHeadGather', 4, '@testHeadGatherTelegram', 'testHeadGather', 'Ольга Вакулина', false), +('$2y$10$GvW6IPLIAda8K7cIVeQ02.Sh/s5hPi2QHXZRSxkwG0kZiG00qOyQi', 'TestHeadMedia', 5, '@testHeadMediaTelegram', 'testHeadMediaTelegram', 'Егор Федорук', false); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +truncate table member CASCADE; + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/010_insert_feed.sql b/migrations/010_insert_feed.sql new file mode 100644 index 0000000..057a693 --- /dev/null +++ b/migrations/010_insert_feed.sql @@ -0,0 +1,17 @@ +-- +goose Up +-- +goose StatementBegin + +insert into feed (title, approved, description, media_id, vk_post_url, updated_at, created_at, views, created_by) +values +('РЕГИСТРАЦИЯ НА СОПК', true, '🔥 Сегодня стартовала приемная кампания 2024 года, и МГТУ им. Н.Э. Баумана радушно распахнул свои двери для будущих студентов. В мае прошло несколько этапов отбора сотрудников, и сегодня они уже помогают абитуриентам с подачей документов, выбором направлений и консультациями. Ректор — Михаил Валерьевич Гордин и и.о. проректора по молодежной работе и воспитательной деятельности — Дмитрий Андреевич Сулегин (https://t.me/sulegin_bmstu) обратились к сотрудникам с напутственными словами и зарядили ребят на продуктивную работу этим летом!🫶 Сотрудники СОПК хорошо понимают все страхи абитуриентов, ведь сами проходили через этап поступления. Ребята ждут вас в Университете, чтобы помочь и ответить на все вопросы. До скорой встречи в МГТУ им. Н.Э. Баумана!', 6, 'https://vk.com/wall-26724538_19086', current_date, current_date, 10, 1), +('РЕГИСТРАЦИЯ НА ШПШ', true, 'На ШМБ приезжают Бауманцы, которые превращают это мероприятие в незабываемое событие для всех первокурсников. Их работа начинается на «Школе Перед Школой», где они не просто знакомятся, но и становятся крепкой командой. Каждая роль здесь важна, и каждый участник вносит неоценимый вклад, выкладываясь на все 100%, чтобы ШМБ стало ярким и запоминающимся мероприятием для всех!',7, 'https://vk.com/wall-26724538_19086', current_date, current_date, 10, 1), +('РЕГИСТРАЦИЯ В СТРОЙ ОТРЯД!', true, '🔥 Сегодня стартовал набор в стройотряд ДрУжБа', 8, 'https://vk.com/wall-26724538_19086', current_date, current_date, 10, 1); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +truncate table feed CASCADE; + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/011_insert_club.sql b/migrations/011_insert_club.sql new file mode 100644 index 0000000..2344101 --- /dev/null +++ b/migrations/011_insert_club.sql @@ -0,0 +1,72 @@ +-- +goose Up +-- +goose StatementBegin + +INSERT INTO club (name, short_name, description, type, logo, vk_url, tg_url) +VALUES + ('SSU IT-department', 'IT-dep', 'Тувинский блогер, не отягощённый интеллектом, решил искупаться в аквариуме с рыбами в гипермаркете "О’кей" в Красноярске. Свой поступок он объяснил фразой "Жарко было на улице, хотел купаться". Теперь "О’кей" хочет засудить молодого человека. Предварительно в магазине подсчитали сумму ущерба – чуть меньше 10 тысяч рублей. В эту стоимость входит изъятая рыба (более десяти голов различной породы) и дезинфекция аквариума.', 'отдел', 6, 'vk.com', 'tg.me'), + ('Техническая поддержка', 'ТП', 'Новые версии операционных систем Apple научились автоматически обнаруживать утерянные или повреждённые фотографии и видео, которые можно будет восстановить и перенести в библиотеку «Фото» или удалить навсегда. Фотографии и видео могут быть утеряны по ряду причин: от повреждения базы данных до ошибок при сохранении фото после съёмки.', 'отдел', 5, 'vk.com', 'tg.me'), + ('Bauman Active Sport', 'BAS', '• несмотря на весь скепсис, сам геймплей оставляет положительное впечатление. Получилась некая смесь Destiny 2 и Overwatch 2, что уже неплохо; +• в игре 16 героев, каждый из которых, якобы, обладает уникальными способностями. Но это не так. Все они повторяют навыки из Overwatch и других схожих проектов; +• вторичны не только навыки, но и сами герои: стрелок с револьвером, танк-щитовик, ракетомётчик с джетпаком и так далее. Складывается чувство, что это китайский клон, а не крупный проект от Sony; +• игровые режимы также не предлагают ничего нового — сражения 5х5 со сбором жетонов или простым уничтожением. Но самое главное, не ощущается командной работы; +• сюжет подаётся через дорогие катсцены, но смотреть их просто невозможно, из-за карикатурных и стереотипных персонажей; +• за опыт, полученный во время матчей, открываются различные косметические предметы, которые можно повесить на героев и оружие.', 'клуб', 7, 'vk.com', 'tg.me'), + ('HR-department', 'HR-dep', 'Аналитическая компания Circana сообщает, что PlayStation Portal возглавила рейтинг продаж игровых аксессуаров в США. Успех стриминговой портативки объясняется общим спадом на рынке и отсутствием аналогов. В США и Великобритании консоль распродали за два дня, а в Испании устройство в три раза популярнее Xbox Series X/S. ', 'HR', 4, 'vk.com', 'tg.me'), + ('КаФеДрА ЮмОрА', 'раш на минималках', 'Собираются пацыки на сходке. Самый главный по автозвуку зовет трех корешей и говорит: +-Кентафарик, попробуй дешево установить автозвук. +Кентафарик тужится, пыжится. +-Никак не получается, товарищ главарь. +-Шлягер, помогай Кентафарику. +Вдвоем пытаются. +-Никак не получается, товарищ главарь. +-Дружище, ну-ка покажи как надо. +Втроем пытаются что-то сделать, та же ситуация. +-А хули вы хотели 18 миллионов', 'Marketing' , 1, 'vk.com', 'tg.me'), + ('Sales-department', 'Sales-dep', 'Typo Sa', 'Sales', 2, 'vk.com', 'tg.me'), + ('Engineering-department', 'Engineering-dep', 'Typo Eng', 'Engineering', 3, 'vk.com', 'tg.me'); + +INSERT INTO club_role (role_name, role_spec, role_clearance) +VALUES +('Бэкендер', 'spec', 1), +('Фронтендер', 'spec', 2), +('Мидлендер', 'spec', 2); + +INSERT INTO club_photo (media_id, club_id) +VALUES +(1, 1), +(1, 2), +(2, 2), +(3, 3), +(4, 4), +(5, 5), +(6, 6), +(7, 7); + +INSERT INTO club_org (club_id, member_id, role_id) +VALUES +(1, 1, 1), +(1, 2, 2), +(1, 3, 3), +(2, 4, 1), +(2, 5, 2), +(2, 1, 3), +(3, 2, 1); + +INSERT INTO encounter (count, description, club_id) +VALUES +('100', 'First encounter', 1), +('200', 'Second encounter', 2), +('300', 'Third encounter', 3); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +truncate table club_role CASCADE; +truncate table club_photo CASCADE; +truncate table club_org CASCADE; +truncate table encounter CASCADE; +truncate table club CASCADE; + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/012_insert_event.sql b/migrations/012_insert_event.sql new file mode 100644 index 0000000..6abeac5 --- /dev/null +++ b/migrations/012_insert_event.sql @@ -0,0 +1,17 @@ +-- +goose Up +-- +goose StatementBegin + +INSERT INTO event (title, description, prompt, media_id, date, approved, created_at, club_id, main_org, reg_url, reg_open_date, feedback_url) +VALUES +('Event 1', 'Description of Event 1', 'Prompt for Event 1', 1, '2022-01-01 10:00:00', true, '2022-01-01 09:00:00', 1, 1, 'https://example.com/register1', '2022-01-01 08:00:00', 'https://example.com/feedback1'), +('Event 2', 'Description of Event 2', 'Prompt for Event 2', 2, '2022-02-01 10:00:00', false, '2022-02-01 09:00:00', 2, 2, 'https://example.com/register2', '2022-02-01 08:00:00', 'https://example.com/feedback2'), +('Event 3', 'Description of Event 3', 'Prompt for Event 3', 3, '2022-03-01 10:00:00', true, '2022-03-01 09:00:00', 3, 3, 'https://example.com/register3', '2022-03-01 08:00:00', 'https://example.com/feedback3'); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +truncate table event CASCADE; + +-- +goose StatementEnd \ No newline at end of file diff --git a/migrations/012_insert_documents_test_data.sql b/migrations/013_insert_document.sql similarity index 52% rename from migrations/012_insert_documents_test_data.sql rename to migrations/013_insert_document.sql index e2aca5f..f9682eb 100644 --- a/migrations/012_insert_documents_test_data.sql +++ b/migrations/013_insert_document.sql @@ -3,20 +3,22 @@ INSERT INTO category (name) VALUES -('first'), -('second'), -('third'); +('Category 1'), +('Category 2'), +('Category 3'); INSERT INTO document (name, key, club_id, category_id) VALUES -('admin', 'admin', 0, 1), -('user', 'user', 1, 1), -('manager','manager', 1, 2); +('1.pdf', '1/1.pdf', 1, 1), +('2.pdf', '2/2.pdf', 2, 2), +('3.pdf', '3/3.pdf', 3, 3); -- +goose StatementEnd -- +goose Down -- +goose StatementBegin -DELETE FROM document; -DELETE FROM category; + +truncate table document CASCADE; +truncate table category CASCADE; + -- +goose StatementEnd \ No newline at end of file diff --git a/migrations/015_insert_default_media.sql b/migrations/015_insert_default_media.sql deleted file mode 100644 index a83c4e3..0000000 --- a/migrations/015_insert_default_media.sql +++ /dev/null @@ -1,19 +0,0 @@ --- +goose Up --- +goose StatementBegin -INSERT INTO default_media (media_id) -VALUES -(1), -(1), -(1), -(1), -(1), -(1), -(1), -(1); - --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DELETE FROM default_media --- +goose StatementEnd diff --git a/pkg/handler/response.go b/pkg/handler/response.go index d4ba5aa..474544d 100644 --- a/pkg/handler/response.go +++ b/pkg/handler/response.go @@ -49,6 +49,10 @@ func UnauthorizedResponse() Response { return &response{head: map[string]string{}, body: nil, code: http.StatusUnauthorized} } +func ForbiddenResponse() Response { + return &response{head: map[string]string{}, body: nil, code: http.StatusForbidden} +} + func ConflictResponse() Response { return &response{head: map[string]string{}, body: nil, code: http.StatusConflict} } diff --git a/pkg/minio/Dockerfile b/pkg/minio/Dockerfile index c42fa71..c7fa8d0 100644 --- a/pkg/minio/Dockerfile +++ b/pkg/minio/Dockerfile @@ -33,9 +33,7 @@ RUN \ COPY ./scripts/minio_migration.sh . RUN mkdir /data -COPY ./data/main_vid.mp4 ./data -COPY ./about/arch.png ./data -COPY ./about/arch.puml ./data +COPY ./data/* ./data RUN chmod 777 minio_migration.sh diff --git a/scripts/minio_migration.sh b/scripts/minio_migration.sh index ec2d950..a607cb7 100644 --- a/scripts/minio_migration.sh +++ b/scripts/minio_migration.sh @@ -7,6 +7,19 @@ mc mb --ignore-existing minio/"$IMAGE_BUCKET" mc anonymous set public minio/"$IMAGE_BUCKET" mc mb --ignore-existing minio/"$DOCUMENT_BUCKET" mc anonymous set public minio/"$DOCUMENT_BUCKET" -mc put data/main_vid.mp4 minio/"$VIDEO_BUCKET" -mc put data/arch.png minio/"$IMAGE_BUCKET" -mc put data/arch.puml minio/"$DOCUMENT_BUCKET" \ No newline at end of file + +for file in data/*.jpg; do + if [ -f "$file" ]; then + mc put "$file" minio/"$IMAGE_BUCKET" + fi +done + +for file in data/*.mp4; do + if [ -f "$file" ]; then + mc put "$file" minio/"$VIDEO_BUCKET" + fi +done + +mc put data/1.pdf minio/"$DOCUMENT_BUCKET"/1 +mc put data/2.pdf minio/"$DOCUMENT_BUCKET"/2 +mc put data/3.pdf minio/"$DOCUMENT_BUCKET"/3 \ No newline at end of file