From 52f671cb030f46edd6ee7fdf19582cbb91d7bcd9 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 12:07:40 -0400 Subject: [PATCH 01/15] merge: type responses dans response.go --- apiresponse/apiresponse.go | 19 ------- apiresponse/bucket.go | 15 ------ apiresponse/document.go | 35 ------------ apiresponse/seed.go | 8 --- apiresponse/spec.go | 8 --- response.go | 107 +++++++++++++++++++++++++++++++++++++ webhandler/webhandler.go | 10 ++-- webresponse/webresponse.go | 31 ----------- 8 files changed, 111 insertions(+), 122 deletions(-) delete mode 100644 apiresponse/apiresponse.go delete mode 100644 apiresponse/bucket.go delete mode 100644 apiresponse/document.go delete mode 100644 apiresponse/seed.go delete mode 100644 apiresponse/spec.go create mode 100644 response.go delete mode 100644 webresponse/webresponse.go diff --git a/apiresponse/apiresponse.go b/apiresponse/apiresponse.go deleted file mode 100644 index f650728..0000000 --- a/apiresponse/apiresponse.go +++ /dev/null @@ -1,19 +0,0 @@ -package apiresponse - -import ( - "codeberg.org/vlbeaudoin/voki/v3" -) - -type APIResponse struct { - voki.MessageResponse - statusCode int - Error string -} - -func (R APIResponse) StatusCode() int { - return R.statusCode -} - -func (R *APIResponse) SetStatusCode(code int) { - R.statusCode = code -} diff --git a/apiresponse/bucket.go b/apiresponse/bucket.go deleted file mode 100644 index 24313dc..0000000 --- a/apiresponse/bucket.go +++ /dev/null @@ -1,15 +0,0 @@ -package apiresponse - -type V1BucketsGET struct { - APIResponse - Data struct { - Buckets map[string]string - } -} - -type V1BucketGET struct { - APIResponse - Data struct { - Keys []string - } -} diff --git a/apiresponse/document.go b/apiresponse/document.go deleted file mode 100644 index 643e54e..0000000 --- a/apiresponse/document.go +++ /dev/null @@ -1,35 +0,0 @@ -package apiresponse - -type DataDocument struct { - Key string - Size int64 -} - -type V1DocumentsPOST struct { - APIResponse - Data struct { - Bucket string - Documents []DataDocument - } -} - -// Deprecated: Use V1DocumentsPOST instead -type V1DocumentPOST struct { - APIResponse - Data struct { - Bucket string - DataDocument - } -} - -type V1DocumentDELETE struct { - APIResponse -} - -type V1DocumentKeyPUT struct { - APIResponse - Data struct { - Bucket string - Key string - } -} diff --git a/apiresponse/seed.go b/apiresponse/seed.go deleted file mode 100644 index 623613c..0000000 --- a/apiresponse/seed.go +++ /dev/null @@ -1,8 +0,0 @@ -package apiresponse - -type V1SeedPOST struct { - APIResponse - Data struct { - Buckets []string - } -} diff --git a/apiresponse/spec.go b/apiresponse/spec.go deleted file mode 100644 index a426f9a..0000000 --- a/apiresponse/spec.go +++ /dev/null @@ -1,8 +0,0 @@ -package apiresponse - -type V1SpecGET struct { - APIResponse - Data struct { - Spec string - } -} diff --git a/response.go b/response.go new file mode 100644 index 0000000..717ff10 --- /dev/null +++ b/response.go @@ -0,0 +1,107 @@ +package main + +import ( + "codeberg.org/vlbeaudoin/voki/v3" + "git.agecem.com/agecem/agecem-org/models" +) + +type APIResponse struct { + voki.MessageResponse + statusCode int + Error string +} + +func (R APIResponse) StatusCode() int { + return R.statusCode +} + +func (R *APIResponse) SetStatusCode(code int) { + R.statusCode = code +} + +type V1BucketsGET struct { + APIResponse + Data struct { + Buckets map[string]string + } +} + +type V1BucketGET struct { + APIResponse + Data struct { + Keys []string + } +} + +type DataDocument struct { + Key string + Size int64 +} + +type V1DocumentsPOST struct { + APIResponse + Data struct { + Bucket string + Documents []DataDocument + } +} + +// Deprecated: Use V1DocumentsPOST instead +type V1DocumentPOST struct { + APIResponse + Data struct { + Bucket string + DataDocument + } +} + +type V1DocumentDELETE struct { + APIResponse +} + +type V1DocumentKeyPUT struct { + APIResponse + Data struct { + Bucket string + Key string + } +} + +type V1SeedPOST struct { + APIResponse + Data struct { + Buckets []string + } +} + +type V1SpecGET struct { + APIResponse + Data struct { + Spec string + } +} + +type HandleAdminDocumentsUploadResponse struct { + APIResponse + Data struct { + Buckets []models.Bucket + } +} + +type HandleDocumentationResponse struct { + APIResponse + Data struct { + Buckets []models.Bucket + } +} + +type UploadDocumentAPIResponse struct { + APIResponse + Data UploadDocumentResponseData +} + +type UploadDocumentResponseData struct { + Bucket string + Object string + Size float64 +} diff --git a/webhandler/webhandler.go b/webhandler/webhandler.go index 9dd8fee..7f75e63 100644 --- a/webhandler/webhandler.go +++ b/webhandler/webhandler.go @@ -10,9 +10,7 @@ import ( "codeberg.org/vlbeaudoin/voki/v3" "git.agecem.com/agecem/agecem-org/api" "git.agecem.com/agecem/agecem-org/apirequest" - "git.agecem.com/agecem/agecem-org/apiresponse" "git.agecem.com/agecem/agecem-org/models" - "git.agecem.com/agecem/agecem-org/webresponse" "github.com/labstack/echo/v4" ) @@ -53,7 +51,7 @@ func HandleVieEtudianteOrganisme(c echo.Context) error { } func (h *WebHandler) HandleDocumentation(c echo.Context) error { - var response webresponse.HandleDocumentationResponse + var response HandleDocumentationResponse v1BucketsGET, err := h.ApiClient.ListBuckets() if err != nil { @@ -68,7 +66,7 @@ func (h *WebHandler) HandleDocumentation(c echo.Context) error { for bucket, displayName := range v1BucketsGET.Data.Buckets { // TODO move call to dedicated API client method - var v1BucketReadResponse apiresponse.V1BucketGET + var v1BucketReadResponse V1BucketGET if err = h.ApiClient.Voki.Unmarshal(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", bucket), nil, true, &v1BucketReadResponse); err != nil { response.Error = err.Error() @@ -136,7 +134,7 @@ func HandleAdmin(c echo.Context) error { } func (h *WebHandler) HandleAdminDocumentsUpload(c echo.Context) error { - var response webresponse.HandleAdminDocumentsUploadResponse + var response HandleAdminDocumentsUploadResponse v1BucketsGET, err := h.ApiClient.ListBuckets() if err != nil { @@ -160,7 +158,7 @@ func (h *WebHandler) HandleAdminDocumentsUpload(c echo.Context) error { func (h *WebHandler) HandleAdminDocumentsUploadPOST(c echo.Context) error { var request apirequest.V1DocumentsPOST - var response webresponse.HandleAdminDocumentsUploadResponse + var response HandleAdminDocumentsUploadResponse v1BucketsGET, err := h.ApiClient.ListBuckets() if err != nil { diff --git a/webresponse/webresponse.go b/webresponse/webresponse.go deleted file mode 100644 index a529e80..0000000 --- a/webresponse/webresponse.go +++ /dev/null @@ -1,31 +0,0 @@ -package webresponse - -import ( - "git.agecem.com/agecem/agecem-org/apiresponse" - "git.agecem.com/agecem/agecem-org/models" -) - -type HandleAdminDocumentsUploadResponse struct { - apiresponse.APIResponse - Data struct { - Buckets []models.Bucket - } -} - -type HandleDocumentationResponse struct { - apiresponse.APIResponse - Data struct { - Buckets []models.Bucket - } -} - -type UploadDocumentAPIResponse struct { - apiresponse.APIResponse - Data UploadDocumentResponseData -} - -type UploadDocumentResponseData struct { - Bucket string - Object string - Size float64 -} From 53f41d64a8dce0d6ef292dddbde27fbcf360999f Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 12:26:32 -0400 Subject: [PATCH 02/15] rename response objects --- response.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/response.go b/response.go index 717ff10..42e9b79 100644 --- a/response.go +++ b/response.go @@ -19,14 +19,14 @@ func (R *APIResponse) SetStatusCode(code int) { R.statusCode = code } -type V1BucketsGET struct { +type ListBucketsResponse struct { APIResponse Data struct { Buckets map[string]string } } -type V1BucketGET struct { +type ReadBucketResponse struct { APIResponse Data struct { Keys []string @@ -38,7 +38,7 @@ type DataDocument struct { Size int64 } -type V1DocumentsPOST struct { +type CreateDocumentsResponse struct { APIResponse Data struct { Bucket string @@ -46,8 +46,7 @@ type V1DocumentsPOST struct { } } -// Deprecated: Use V1DocumentsPOST instead -type V1DocumentPOST struct { +type CreateDocumentResponse struct { APIResponse Data struct { Bucket string @@ -55,11 +54,11 @@ type V1DocumentPOST struct { } } -type V1DocumentDELETE struct { +type DeleteDocumentResponse struct { APIResponse } -type V1DocumentKeyPUT struct { +type UpdateDocumentKeyResponse struct { APIResponse Data struct { Bucket string @@ -67,20 +66,21 @@ type V1DocumentKeyPUT struct { } } -type V1SeedPOST struct { +type ExecuteSeedResponse struct { APIResponse Data struct { Buckets []string } } -type V1SpecGET struct { +type ReadSpecResponse struct { APIResponse Data struct { Spec string } } +// Deprecated: Use ListBucketsResponse instead type HandleAdminDocumentsUploadResponse struct { APIResponse Data struct { @@ -88,6 +88,7 @@ type HandleAdminDocumentsUploadResponse struct { } } +// Deprecated: Use ListBucketsResponse instead type HandleDocumentationResponse struct { APIResponse Data struct { @@ -95,11 +96,13 @@ type HandleDocumentationResponse struct { } } +// Deprecated: Use CreateDocumentResponse instead type UploadDocumentAPIResponse struct { APIResponse Data UploadDocumentResponseData } +// Deprecated: Use DataDocument instead type UploadDocumentResponseData struct { Bucket string Object string From 9e08b3f962ea081e71be1b2389b4702b7188ed01 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 12:33:54 -0400 Subject: [PATCH 03/15] merge: requests dans request.go --- apihandler/document.go | 2 +- apirequest/bucket.go | 57 ------------ apirequest/seed.go | 28 ------ apirequest/spec.go | 28 ------ cmd/server.go | 2 +- apirequest/document.go => request.go | 133 ++++++++++++++++++++++----- webhandler/webhandler.go | 2 +- 7 files changed, 111 insertions(+), 141 deletions(-) delete mode 100644 apirequest/bucket.go delete mode 100644 apirequest/seed.go delete mode 100644 apirequest/spec.go rename apirequest/document.go => request.go (51%) diff --git a/apihandler/document.go b/apihandler/document.go index 2d0a27c..274ba25 100644 --- a/apihandler/document.go +++ b/apihandler/document.go @@ -22,7 +22,7 @@ Téléverser plusieurs fichiers à cette route avec `curl`: curl -F 'documents=@example.pdf' -F 'documents=@example.md;type=text/markdown' */ func (h *V1Handler) V1DocumentsPOST(c echo.Context) (err error) { - var request apirequest.V1DocumentsPOST + var request apirequest.CreateDocumentsResponse var response apiresponse.V1DocumentsPOST request.Params.Bucket = c.Param("bucket") diff --git a/apirequest/bucket.go b/apirequest/bucket.go deleted file mode 100644 index d47c5cc..0000000 --- a/apirequest/bucket.go +++ /dev/null @@ -1,57 +0,0 @@ -package apirequest - -import ( - "fmt" - "net/http" - - "codeberg.org/vlbeaudoin/voki/v3" - "git.agecem.com/agecem/agecem-org/apiresponse" -) - -var _ voki.Requester[apiresponse.V1BucketsGET] = V1BucketsGET{} - -type V1BucketsGET struct{} - -func NewV1BucketsGET() (request V1BucketsGET, err error) { - return -} - -func (request V1BucketsGET) Complete() bool { return true } - -func (request V1BucketsGET) Request(v *voki.Voki) (response apiresponse.V1BucketsGET, err error) { - if !request.Complete() { - err = fmt.Errorf("Incomplete V1BucketsGET request") - return - } - - return response, v.UnmarshalIfComplete(http.MethodGet, "/v1/bucket", nil, true, &response) -} - -var _ voki.Requester[apiresponse.V1BucketGET] = V1BucketGET{} - -type V1BucketGET struct { - Params struct { - Bucket string `json:"bucket"` - } -} - -func NewV1BucketGET(bucket string) (request V1BucketGET, err error) { - if bucket == "" { - err = fmt.Errorf("NewV1BucketGET requires non-nil bucket name") - } - - request.Params.Bucket = bucket - - return -} - -func (request V1BucketGET) Complete() bool { return request.Params.Bucket != "" } - -func (request V1BucketGET) Request(v *voki.Voki) (response apiresponse.V1BucketGET, err error) { - if !request.Complete() { - err = fmt.Errorf("Incomplete V1BucketGET request") - return - } - - return response, v.UnmarshalIfComplete(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", request.Params.Bucket), nil, true, &response) -} diff --git a/apirequest/seed.go b/apirequest/seed.go deleted file mode 100644 index 33ec9d0..0000000 --- a/apirequest/seed.go +++ /dev/null @@ -1,28 +0,0 @@ -package apirequest - -import ( - "fmt" - "net/http" - - "codeberg.org/vlbeaudoin/voki/v3" - "git.agecem.com/agecem/agecem-org/apiresponse" -) - -var _ voki.Requester[apiresponse.V1SeedPOST] = V1SeedPOST{} - -type V1SeedPOST struct{} - -func NewV1SeedPOST() (request V1SeedPOST, err error) { - return -} - -func (r V1SeedPOST) Complete() bool { return true } - -func (r V1SeedPOST) Request(v *voki.Voki) (response apiresponse.V1SeedPOST, err error) { - if !r.Complete() { - err = fmt.Errorf("Incomplete V1SeedPOST") - return - } - - return response, v.UnmarshalIfComplete(http.MethodPost, "/v1/seed", nil, true, &response) -} diff --git a/apirequest/spec.go b/apirequest/spec.go deleted file mode 100644 index d468cdd..0000000 --- a/apirequest/spec.go +++ /dev/null @@ -1,28 +0,0 @@ -package apirequest - -import ( - "fmt" - "net/http" - - "codeberg.org/vlbeaudoin/voki/v3" - "git.agecem.com/agecem/agecem-org/apiresponse" -) - -var _ voki.Requester[apiresponse.V1SpecGET] = V1SpecGET{} - -type V1SpecGET struct{} - -func NewV1SpecGET() (request V1SpecGET, err error) { - return -} - -func (request V1SpecGET) Complete() bool { return true } - -func (request V1SpecGET) Request(v *voki.Voki) (response apiresponse.V1SpecGET, err error) { - if !request.Complete() { - err = fmt.Errorf("Incomplete V1SpecGET") - return - } - - return response, v.UnmarshalIfComplete(http.MethodGet, "/v1/spec", nil, true, &response) -} diff --git a/cmd/server.go b/cmd/server.go index 7669521..faf6dba 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -250,7 +250,7 @@ func RunServer() { } if err := pave.EchoRegister[ - apirequest.V1DocumentsPOST, + apirequest.CreateDocumentsResponse, apiresponse.V1DocumentsPOST](groupV1, &p, "/v1", http.MethodPost, "/bucket/:bucket/many", "Upload documents to specified bucket", "V1DocumentsPOST", v1Handler.V1DocumentsPOST); err != nil { log.Fatal(err) } diff --git a/apirequest/document.go b/request.go similarity index 51% rename from apirequest/document.go rename to request.go index f69c413..41f879f 100644 --- a/apirequest/document.go +++ b/request.go @@ -1,4 +1,4 @@ -package apirequest +package main import ( "bytes" @@ -8,12 +8,59 @@ import ( "net/http" "codeberg.org/vlbeaudoin/voki/v3" - "git.agecem.com/agecem/agecem-org/apiresponse" ) -var _ voki.Requester[apiresponse.V1DocumentsPOST] = V1DocumentsPOST{} +var _ voki.Requester[ListBucketsResponse] = ListBucketsRequest{} -type V1DocumentsPOST struct { +type ListBucketsRequest struct{} + +func NewV1BucketsGET() (request ListBucketsRequest, err error) { + return +} + +func (request ListBucketsRequest) Complete() bool { return true } + +func (request ListBucketsRequest) Request(v *voki.Voki) (response ListBucketsResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete V1BucketsGET request") + return + } + + return response, v.UnmarshalIfComplete(http.MethodGet, "/v1/bucket", nil, true, &response) +} + +var _ voki.Requester[ReadBucketResponse] = ReadBucketRequest{} + +type ReadBucketRequest struct { + Params struct { + Bucket string `json:"bucket"` + } +} + +func NewV1BucketGET(bucket string) (request ReadBucketRequest, err error) { + if bucket == "" { + err = fmt.Errorf("NewV1BucketGET requires non-nil bucket name") + } + + request.Params.Bucket = bucket + + return +} + +func (request ReadBucketRequest) Complete() bool { return request.Params.Bucket != "" } + +func (request ReadBucketRequest) Request(v *voki.Voki) (response ReadBucketResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete V1BucketGET request") + return + } + + return response, v.UnmarshalIfComplete(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", request.Params.Bucket), nil, true, &response) +} + +var _ voki.Requester[CreateDocumentsResponse] = CreateDocumentsRequest{} + +type CreateDocumentsRequest struct { Data struct { Documents []*multipart.FileHeader `json:"documents"` } @@ -22,7 +69,7 @@ type V1DocumentsPOST struct { } } -func NewV1DocumentsPOST(bucket string, documents ...*multipart.FileHeader) (request V1DocumentsPOST, err error) { +func NewV1DocumentsPOST(bucket string, documents ...*multipart.FileHeader) (request CreateDocumentsRequest, err error) { if bucket == "" { err = fmt.Errorf("NewV1DocumentsPOST requires non-nil bucket name") return @@ -47,7 +94,7 @@ func NewV1DocumentsPOST(bucket string, documents ...*multipart.FileHeader) (requ return } -func (request V1DocumentsPOST) Complete() bool { +func (request CreateDocumentsRequest) Complete() bool { if request.Data.Documents == nil { return false } @@ -60,7 +107,7 @@ func (request V1DocumentsPOST) Complete() bool { return request.Params.Bucket != "" } -func (request V1DocumentsPOST) Request(v *voki.Voki) (response apiresponse.V1DocumentsPOST, err error) { +func (request CreateDocumentsRequest) Request(v *voki.Voki) (response CreateDocumentsResponse, err error) { if !request.Complete() { err = fmt.Errorf("Incomplete V1DocumentsPOST request") return @@ -74,20 +121,18 @@ func (request V1DocumentsPOST) Request(v *voki.Voki) (response apiresponse.V1Doc return response, v.UnmarshalIfComplete(http.MethodPost, fmt.Sprintf("/v1/bucket/%s/many", request.Params.Bucket), &buf, true, &response) } -var _ voki.Requester[apiresponse.V1DocumentPOST] = V1DocumentPOST{} +var _ voki.Requester[CreateDocumentResponse] = CreateDocumentRequest{} -// Deprecated: Use V1DocumentsPOST instead -type V1DocumentPOST struct { +type CreateDocumentRequest struct { Data struct { - Document *multipart.FileHeader `document` + Document *multipart.FileHeader `json:"document"` } Params struct { Bucket string `json:"bucket"` } } -// Deprecated: Use NewV1DocumentsPOST instead -func NewV1DocumentPOST(bucket string, document *multipart.FileHeader) (request V1DocumentPOST, err error) { +func NewV1DocumentPOST(bucket string, document *multipart.FileHeader) (request CreateDocumentRequest, err error) { if bucket == "" { err = fmt.Errorf("NewV1DocumentPOST requires non-nil bucket name") return @@ -105,11 +150,11 @@ func NewV1DocumentPOST(bucket string, document *multipart.FileHeader) (request V return } -func (request V1DocumentPOST) Complete() bool { +func (request CreateDocumentRequest) Complete() bool { return request.Params.Bucket != "" && request.Data.Document != nil } -func (request V1DocumentPOST) Request(v *voki.Voki) (response apiresponse.V1DocumentPOST, err error) { +func (request CreateDocumentRequest) Request(v *voki.Voki) (response CreateDocumentResponse, err error) { if !request.Complete() { err = fmt.Errorf("Incomplete V1DocumentPOST request") return @@ -123,16 +168,16 @@ func (request V1DocumentPOST) Request(v *voki.Voki) (response apiresponse.V1Docu return response, v.UnmarshalIfComplete(http.MethodPost, fmt.Sprintf("/v1/bucket/%s", request.Params.Bucket), &buf, true, &response) } -var _ voki.Requester[apiresponse.V1DocumentDELETE] = V1DocumentDELETE{} +var _ voki.Requester[DeleteDocumentResponse] = DeleteDocumentRequest{} -type V1DocumentDELETE struct { +type DeleteDocumentRequest struct { Params struct { Bucket string `json:"bucket"` Document string `json:"document"` } } -func NewV1DocumentDELETE(bucket, document string) (request V1DocumentDELETE, err error) { +func NewV1DocumentDELETE(bucket, document string) (request DeleteDocumentRequest, err error) { if bucket == "" { err = fmt.Errorf("NewV1DocumentDELETE requires non-nil bucket name") return @@ -150,11 +195,11 @@ func NewV1DocumentDELETE(bucket, document string) (request V1DocumentDELETE, err return } -func (request V1DocumentDELETE) Complete() bool { +func (request DeleteDocumentRequest) Complete() bool { return request.Params.Bucket != "" && request.Params.Document != "" } -func (request V1DocumentDELETE) Request(v *voki.Voki) (response apiresponse.V1DocumentDELETE, err error) { +func (request DeleteDocumentRequest) Request(v *voki.Voki) (response DeleteDocumentResponse, err error) { if !request.Complete() { err = fmt.Errorf("Incomplete V1DocumentDELETE request") return @@ -163,9 +208,9 @@ func (request V1DocumentDELETE) Request(v *voki.Voki) (response apiresponse.V1Do return response, v.UnmarshalIfComplete(http.MethodDelete, fmt.Sprintf("/v1/bucket/%s/%s", request.Params.Bucket, request.Params.Document), nil, true, &response) } -var _ voki.Requester[apiresponse.V1DocumentKeyPUT] = V1DocumentKeyPUT{} +var _ voki.Requester[UpdateDocumentKeyResponse] = UpdateDocumentKeyRequest{} -type V1DocumentKeyPUT struct { +type UpdateDocumentKeyRequest struct { Data struct { NewKey string `json:"newKey"` } @@ -175,7 +220,7 @@ type V1DocumentKeyPUT struct { } } -func NewV1DocumentKeyPUT(bucket, document, newKey string) (request V1DocumentKeyPUT, err error) { +func NewV1DocumentKeyPUT(bucket, document, newKey string) (request UpdateDocumentKeyRequest, err error) { if bucket == "" { err = fmt.Errorf("NewV1DocumentKeyPUT requires non-nil bucket name") return @@ -199,11 +244,11 @@ func NewV1DocumentKeyPUT(bucket, document, newKey string) (request V1DocumentKey return } -func (request V1DocumentKeyPUT) Complete() bool { +func (request UpdateDocumentKeyRequest) Complete() bool { return request.Params.Bucket != "" && request.Params.Document != "" && request.Data.NewKey != "" } -func (request V1DocumentKeyPUT) Request(v *voki.Voki) (response apiresponse.V1DocumentKeyPUT, err error) { +func (request UpdateDocumentKeyRequest) Request(v *voki.Voki) (response UpdateDocumentKeyResponse, err error) { if !request.Complete() { err = fmt.Errorf("Incomplete V1DocumentKeyPUT request") return @@ -216,3 +261,41 @@ func (request V1DocumentKeyPUT) Request(v *voki.Voki) (response apiresponse.V1Do return response, v.UnmarshalIfComplete(http.MethodPut, fmt.Sprintf("/v1/bucket/%s/%s", request.Params.Bucket, request.Params.Document), &buf, true, &response) } + +var _ voki.Requester[ExecuteSeedResponse] = ExecuteSeedRequest{} + +type ExecuteSeedRequest struct{} + +func NewV1SeedPOST() (request ExecuteSeedRequest, err error) { + return +} + +func (r ExecuteSeedRequest) Complete() bool { return true } + +func (r ExecuteSeedRequest) Request(v *voki.Voki) (response ExecuteSeedResponse, err error) { + if !r.Complete() { + err = fmt.Errorf("Incomplete V1SeedPOST") + return + } + + return response, v.UnmarshalIfComplete(http.MethodPost, "/v1/seed", nil, true, &response) +} + +var _ voki.Requester[ReadSpecResponse] = ReadSpecRequest{} + +type ReadSpecRequest struct{} + +func NewV1SpecGET() (request ReadSpecRequest, err error) { + return +} + +func (request ReadSpecRequest) Complete() bool { return true } + +func (request ReadSpecRequest) Request(v *voki.Voki) (response ReadSpecResponse, err error) { + if !request.Complete() { + err = fmt.Errorf("Incomplete V1SpecGET") + return + } + + return response, v.UnmarshalIfComplete(http.MethodGet, "/v1/spec", nil, true, &response) +} diff --git a/webhandler/webhandler.go b/webhandler/webhandler.go index 7f75e63..082aff2 100644 --- a/webhandler/webhandler.go +++ b/webhandler/webhandler.go @@ -157,7 +157,7 @@ func (h *WebHandler) HandleAdminDocumentsUpload(c echo.Context) error { } func (h *WebHandler) HandleAdminDocumentsUploadPOST(c echo.Context) error { - var request apirequest.V1DocumentsPOST + var request apirequest.CreateDocumentsResponse var response HandleAdminDocumentsUploadResponse v1BucketsGET, err := h.ApiClient.ListBuckets() From b4d5e65edc40953d86707c66b9c8c1c86a51c412 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 12:44:50 -0400 Subject: [PATCH 04/15] docs: ajouter descriptions de fichiers response.go et request.go --- request.go | 5 +++++ response.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/request.go b/request.go index 41f879f..b6e9955 100644 --- a/request.go +++ b/request.go @@ -1,3 +1,8 @@ +/* +File request.go contains the JSON request types for HTTP endpoints. + +Le fichier request.go contient les types de requêtes pour les endpoints HTTP. +*/ package main import ( diff --git a/response.go b/response.go index 42e9b79..bbfed83 100644 --- a/response.go +++ b/response.go @@ -1,3 +1,8 @@ +/* +File response.go contains the JSON and HTML response types for HTTP endpoints. + +Le fichier response.go contient les types de réponses JSON et HTML pour les endpoints HTTP. +*/ package main import ( From 106e903b14c4cc6af10495e3ea7834bb3db7d144 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:10:46 -0400 Subject: [PATCH 05/15] merge: handlers dans handler.go --- apihandler/apihandler.go | 27 -- apihandler/bucket.go | 110 ------ apihandler/document.go | 400 ------------------- apihandler/seed.go | 35 -- apihandler/spec.go | 36 -- handler.go | 806 +++++++++++++++++++++++++++++++++++++++ webhandler/webhandler.go | 254 ------------ 7 files changed, 806 insertions(+), 862 deletions(-) delete mode 100644 apihandler/apihandler.go delete mode 100644 apihandler/bucket.go delete mode 100644 apihandler/document.go delete mode 100644 apihandler/seed.go delete mode 100644 apihandler/spec.go create mode 100644 handler.go delete mode 100644 webhandler/webhandler.go diff --git a/apihandler/apihandler.go b/apihandler/apihandler.go deleted file mode 100644 index 4d0c96b..0000000 --- a/apihandler/apihandler.go +++ /dev/null @@ -1,27 +0,0 @@ -package apihandler - -import ( - "net/http" - "sort" - - "codeberg.org/vlbeaudoin/pave/v2" - "git.agecem.com/agecem/agecem-org/config" - "git.agecem.com/agecem/agecem-org/media" - "github.com/labstack/echo/v4" -) - -type V1Handler struct { - Config config.Config - MediaClient *media.MediaClient - Pave *pave.Pave -} - -// API Handlers - -// V1GET affiche les routes accessibles. -// Les routes sont triées selon .Path, pour les rendre plus facilement navigables. -func (h *V1Handler) V1GET(c echo.Context) error { - routes := c.Echo().Routes() - sort.Slice(routes, func(i, j int) bool { return routes[i].Path < routes[j].Path }) - return c.JSON(http.StatusOK, routes) -} diff --git a/apihandler/bucket.go b/apihandler/bucket.go deleted file mode 100644 index 3cb37ee..0000000 --- a/apihandler/bucket.go +++ /dev/null @@ -1,110 +0,0 @@ -package apihandler - -import ( - "context" - "net/http" - - "codeberg.org/vlbeaudoin/voki/v3" - "git.agecem.com/agecem/agecem-org/apirequest" - "git.agecem.com/agecem/agecem-org/apiresponse" - "github.com/labstack/echo/v4" - "github.com/minio/minio-go/v7" -) - -// V1BucketsGET affiche les buckets permis par server.documents.buckets, qui existent. -func (h *V1Handler) V1BucketsGET(c echo.Context) error { - var request apirequest.V1BucketsGET - var response apiresponse.V1BucketsGET - - if !request.Complete() { - response.Message = "Incomplete V1BucketsGET request received" - response.SetStatusCode(http.StatusBadRequest) - - return c.JSON(response.StatusCode(), response) - } - - var buckets = make(map[string]string) - - for bucket_name, bucket_display_name := range h.Config.Server.Documents.Buckets { - exists, err := h.MediaClient.MinioClient.BucketExists(context.Background(), bucket_name) - if err != nil { - response.Message = "Error during minio#BucketExists" - response.SetStatusCode(http.StatusInternalServerError) - - return c.JSON(response.StatusCode(), response) - } - - if exists { - buckets[bucket_name] = bucket_display_name - } - } - - response.SetStatusCode(http.StatusOK) - response.Message = "Buckets list successful" - response.Data.Buckets = buckets - - return c.JSON(response.StatusCode(), response) -} - -func (h *V1Handler) V1BucketGET(c echo.Context) error { - var request apirequest.V1BucketGET - var response apiresponse.V1BucketGET - - request.Params.Bucket = c.Param("bucket") - - if !request.Complete() { - response.Message = "Incomplete V1BucketGET request received" - response.SetStatusCode(http.StatusBadRequest) - - return c.JSON(response.StatusCode(), response) - } - - allowed := false - for bucket_allowed := range h.Config.Server.Documents.Buckets { - if request.Params.Bucket == bucket_allowed { - allowed = true - } - } - - if !allowed { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - ctx, cancel := context.WithCancel(context.Background()) - - defer cancel() - - exists, err := h.MediaClient.MinioClient.BucketExists(ctx, request.Params.Bucket) - if err != nil { - response.Message = "Error during minio#BucketExists" - response.SetStatusCode(http.StatusInternalServerError) - response.Error = err.Error() - - return c.JSON(response.StatusCode(), response) - } - - if !exists { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - objectCh := h.MediaClient.MinioClient.ListObjects(ctx, request.Params.Bucket, minio.ListObjectsOptions{}) - for object := range objectCh { - if object.Err != nil { - response.Message = "Error during minio#ListObjects" - response.SetStatusCode(http.StatusInternalServerError) - //TODO make sure this is safe - //response.Error = object.Err.Error() - - return c.JSON(response.StatusCode(), response) - } - - response.Data.Keys = append(response.Data.Keys, object.Key) - } - - response.Message = "V1BucketRead ok" - response.SetStatusCode(http.StatusOK) - - return c.JSON(response.StatusCode(), response) -} diff --git a/apihandler/document.go b/apihandler/document.go deleted file mode 100644 index 274ba25..0000000 --- a/apihandler/document.go +++ /dev/null @@ -1,400 +0,0 @@ -package apihandler - -import ( - "context" - "fmt" - "net/http" - - "codeberg.org/vlbeaudoin/voki/v3" - "git.agecem.com/agecem/agecem-org/apirequest" - "git.agecem.com/agecem/agecem-org/apiresponse" - "github.com/labstack/echo/v4" - "github.com/minio/minio-go/v7" -) - -/* -V1DocumentsPOST permet d'ajouter un object dans un bucket, par multipart/form-data - -Example: - -Téléverser plusieurs fichiers à cette route avec `curl`: - - curl -F 'documents=@example.pdf' -F 'documents=@example.md;type=text/markdown' -*/ -func (h *V1Handler) V1DocumentsPOST(c echo.Context) (err error) { - var request apirequest.CreateDocumentsResponse - var response apiresponse.V1DocumentsPOST - - request.Params.Bucket = c.Param("bucket") - - var allowed bool - for allowedBucket := range h.Config.Server.Documents.Buckets { - if request.Params.Bucket == allowedBucket { - allowed = true - } - } - - if !allowed { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - form, err := c.MultipartForm() - if err != nil { - response.Message = fmt.Sprintf("Téléversement invalide: %s", err) - response.SetStatusCode(http.StatusBadRequest) - - return c.JSON(response.StatusCode(), response) - } - if form == nil { - response.Message = "MultipartForm pointe vers une addresse mémoire nil" - response.SetStatusCode(http.StatusBadRequest) - - return c.JSON(response.StatusCode(), response) - } - - if len(form.File) == 0 { - response.Message = "Veuillez sélectionner au moins 1 document à téléverser" - response.SetStatusCode(http.StatusBadRequest) - - return c.JSON(response.StatusCode(), response) - } - - for inputName, inputFileHeaders := range form.File { - if inputName == "documents" { - request.Data.Documents = inputFileHeaders - } - } - - if request.Data.Documents == nil { - response.Message = "Impossible d'obtenir les documents depuis le formulaire" - response.SetStatusCode(http.StatusBadRequest) - - return c.JSON(response.StatusCode(), response) - } - - if !request.Complete() { - response.Message = "Requête V1DocumentsPOST incomplète reçue" - response.SetStatusCode(http.StatusBadRequest) - - return c.JSON(response.StatusCode(), response) - } - - var code int - code, response.Message = h.MediaClient.UploadFormFiles(request.Params.Bucket, request.Data.Documents) - response.SetStatusCode(code) - - return c.JSON(response.StatusCode(), response) -} - -// V1DocumentPOST permet d'ajouter un object dans un bucket, par multipart/form-data -func (h *V1Handler) V1DocumentPOST(c echo.Context) (err error) { - var request apirequest.V1DocumentPOST - var response apiresponse.V1DocumentPOST - - request.Params.Bucket = c.Param("bucket") - - request.Data.Document, err = c.FormFile("document") - if err != nil { - response.SetStatusCode(http.StatusBadRequest) - response.Message = "Error during HandleV1DocumentCreate's echo#Context.FormFile" - response.Error = err.Error() - - return c.JSON(response.StatusCode(), response) - } - - allowed := false - for bucket_allowed := range h.Config.Server.Documents.Buckets { - if request.Params.Bucket == bucket_allowed { - allowed = true - } - } - - if !allowed { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - if !request.Complete() { - response.Message = "Incomplete V1DocumentPOST request received" - response.SetStatusCode(http.StatusBadRequest) - - return c.JSON(response.StatusCode(), response) - } - - ctx, cancel := context.WithCancel(context.Background()) - - defer cancel() - - src, err := request.Data.Document.Open() - if err != nil { - response.SetStatusCode(http.StatusBadRequest) - response.Message = "Error during form_file.Open()" - response.Error = err.Error() - - return c.JSON(response.StatusCode(), response) - } - defer src.Close() - - info, err := h.MediaClient.MinioClient.PutObject(ctx, request.Params.Bucket, request.Data.Document.Filename, src, request.Data.Document.Size, minio.PutObjectOptions{ - ContentType: request.Data.Document.Header.Get("Content-Type"), - }) - if err != nil { - response.SetStatusCode(http.StatusInternalServerError) - response.Message = "Error during minio#PutObject" - //response.Error = err.Error() - - return c.JSON(response.StatusCode(), response) - } - - response.SetStatusCode(http.StatusOK) - response.Message = "ok" - response.Data.Bucket = info.Bucket - response.Data.Key = info.Key - response.Data.Size = info.Size - - return c.JSON(response.StatusCode(), response) -} - -// V1DocumentGET permet de lire le contenu d'un fichier et protentiellement de le télécharger -func (h *V1Handler) V1DocumentGET(c echo.Context) error { - bucket := c.Param("bucket") - document := c.Param("document") - - allowed := false - for bucket_allowed := range h.Config.Server.Documents.Buckets { - if bucket == bucket_allowed { - allowed = true - } - } - - if !allowed { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - ctx, cancel := context.WithCancel(context.Background()) - - defer cancel() - - bucket_exists, err := h.MediaClient.MinioClient.BucketExists(ctx, bucket) - if err != nil { - return c.JSON(http.StatusInternalServerError, "Error during minio#BucketExists") - } - - if !bucket_exists { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - document_info, err := h.MediaClient.MinioClient.StatObject(ctx, bucket, document, minio.StatObjectOptions{}) - - if err != nil { - if err.Error() == "The specified key does not exist." { - - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - return c.JSON(http.StatusInternalServerError, map[string]interface{}{ - "message": "Error during minio#StatObject", - }) - } - - _ = document_info - - document_object, err := h.MediaClient.MinioClient.GetObject(ctx, bucket, document, minio.GetObjectOptions{}) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{ - "message": "Error during minio#GetObject", - }) - } - - defer document_object.Close() - - return c.Stream(http.StatusOK, document_info.ContentType, document_object) -} - -// V1DocumentDELETE permet de supprimer un object -func (h *V1Handler) V1DocumentDELETE(c echo.Context) error { - var request apirequest.V1DocumentDELETE - var response apiresponse.V1DocumentDELETE - - request.Params.Bucket = c.Param("bucket") - request.Params.Document = c.Param("document") - - allowed := false - for bucket_allowed := range h.Config.Server.Documents.Buckets { - if request.Params.Bucket == bucket_allowed { - allowed = true - } - } - - if !allowed { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - if !request.Complete() { - response.Message = "Incomplete V1DocumentDELETE request received" - response.SetStatusCode(http.StatusBadRequest) - - return c.JSON(response.StatusCode(), response) - } - - ctx, cancel := context.WithCancel(context.Background()) - - defer cancel() - - bucket_exists, err := h.MediaClient.MinioClient.BucketExists(ctx, request.Params.Bucket) - if err != nil { - return c.JSON(http.StatusInternalServerError, "Error during minio#BucketExists") - } - - if !bucket_exists { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - document_info, err := h.MediaClient.MinioClient.StatObject(ctx, request.Params.Bucket, request.Params.Document, minio.StatObjectOptions{}) - if err != nil { - if err.Error() == "The specified key does not exist." { - - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - //response.Error = err.Error() - response.Message = "Error during minio#StatObject" - response.SetStatusCode(http.StatusInternalServerError) - - return c.JSON(response.StatusCode(), response) - } - - //TODO Add error validation - _ = document_info - - err = h.MediaClient.MinioClient.RemoveObject(ctx, request.Params.Bucket, request.Params.Document, minio.RemoveObjectOptions{}) - if err != nil { - //response.Error = err.Error() - response.Message = "Error during minio#RemoveObject" - response.SetStatusCode(http.StatusInternalServerError) - - return c.JSON(response.StatusCode(), response) - } - - response.Message = "Document deleted" - response.SetStatusCode(http.StatusOK) - - return c.JSON(response.StatusCode(), response) -} - -// V1DocumentKeyPUT -func (h *V1Handler) V1DocumentKeyPUT(c echo.Context) (err error) { - var request apirequest.V1DocumentKeyPUT - var response apiresponse.V1DocumentKeyPUT - - bucket := c.Param("bucket") - document := c.Param("document") - - var newKey string - err = c.Bind(&newKey) - if err != nil { - response.SetStatusCode(http.StatusBadRequest) - response.Message = err.Error() - - return c.JSON(response.StatusCode(), response) - } - - request, err = apirequest.NewV1DocumentKeyPUT(bucket, document, newKey) - if err != nil { - response.SetStatusCode(http.StatusBadRequest) - response.Message = err.Error() - - return c.JSON(response.StatusCode(), response) - } - - if !request.Complete() { - response.SetStatusCode(http.StatusBadRequest) - response.Message = "Incomplete V1DocumentKeyPUT request received" - - return c.JSON(response.StatusCode(), response) - } - - var allowed bool - for bucketAllowed := range h.Config.Server.Documents.Buckets { - if bucket == bucketAllowed { - allowed = true - } - } - - if !allowed { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - bucketExists, err := h.MediaClient.MinioClient.BucketExists(ctx, request.Params.Bucket) - if err != nil { - return c.JSON(http.StatusInternalServerError, "Could not validate bucket exists") - } - - if !bucketExists { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - // Check source object exists - - if _, err := h.MediaClient.MinioClient.StatObject(ctx, request.Params.Bucket, request.Params.Document, minio.StatObjectOptions{}); err != nil { - if err.Error() == "The specified key does not exist." { - - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - - response.Message = fmt.Sprintf("Could not obtain information on %s/%s", request.Params.Bucket, request.Params.Document) - response.SetStatusCode(http.StatusInternalServerError) - - return c.JSON(response.StatusCode(), response) - } - - // Copy object to newKey - - if _, err := h.MediaClient.MinioClient.CopyObject(ctx, - minio.CopyDestOptions{Bucket: request.Params.Bucket, Object: request.Data.NewKey}, - minio.CopySrcOptions{Bucket: request.Params.Bucket, Object: request.Params.Document}, - ); err != nil { - response.SetStatusCode(http.StatusInternalServerError) - response.Message = "Impossible de copier un document pour le renommer" - return c.JSON(response.StatusCode(), response) - } - - // Verify copy was successful - if _, err := h.MediaClient.MinioClient.StatObject(ctx, - request.Params.Bucket, request.Data.NewKey, minio.GetObjectOptions{}); err != nil { - response.SetStatusCode(http.StatusInternalServerError) - response.Message = "Copie de document ne semble pas avoir fonctionnée" - return c.JSON(response.StatusCode(), response) - } - - // Delete old file - if err := h.MediaClient.MinioClient.RemoveObject(ctx, - request.Params.Bucket, request.Params.Document, minio.RemoveObjectOptions{}); err != nil { - response.SetStatusCode(http.StatusInternalServerError) - response.Message = "Erreur pendant tentative de supprimer document source" - return c.JSON(response.StatusCode(), response) - } - - //TODO cleanup - - // Return result - response.SetStatusCode(http.StatusOK) - response.Message = "Document renommé avec succès" - response.Data.Bucket = request.Params.Bucket - response.Data.Key = request.Data.NewKey - return c.JSON(response.StatusCode(), response) - -} diff --git a/apihandler/seed.go b/apihandler/seed.go deleted file mode 100644 index 9462eca..0000000 --- a/apihandler/seed.go +++ /dev/null @@ -1,35 +0,0 @@ -package apihandler - -import ( - "net/http" - - "git.agecem.com/agecem/agecem-org/apiresponse" - "github.com/labstack/echo/v4" -) - -// V1SeedPOST créé des buckets dans minio selon la liste de buckets dans server.documents.buckets -// Les buckets sont créés avec paramètres par défaut, et sont ensuite visible dans /v1/bucket. -func (h *V1Handler) V1SeedPOST(c echo.Context) error { - var response apiresponse.V1SeedPOST - - new_buckets, err := h.MediaClient.Seed() - response.Data.Buckets = new_buckets - if err != nil { - response.SetStatusCode(http.StatusInternalServerError) - response.Message = "Error during mediaClient.Seed()" - response.Error = err.Error() - - return c.JSON(response.StatusCode(), response) - } - - if len(new_buckets) == 0 { - response.Message = "All buckets already exist" - - } else { - response.Message = "Buckets successfully created" - } - - response.SetStatusCode(http.StatusOK) - - return c.JSON(response.StatusCode(), response) -} diff --git a/apihandler/spec.go b/apihandler/spec.go deleted file mode 100644 index afc69c4..0000000 --- a/apihandler/spec.go +++ /dev/null @@ -1,36 +0,0 @@ -package apihandler - -import ( - "fmt" - "net/http" - - "git.agecem.com/agecem/agecem-org/apirequest" - "git.agecem.com/agecem/agecem-org/apiresponse" - "git.agecem.com/agecem/agecem-org/version" - "github.com/labstack/echo/v4" -) - -const DescriptionV1SpecGET string = "Afficher le API spec en format pave" - -func (h *V1Handler) V1SpecGET(c echo.Context) error { - var request apirequest.V1SpecGET - var response apiresponse.V1SpecGET - - if !request.Complete() { - response.Message = "Incomplete V1SpecGET request received" - response.SetStatusCode(http.StatusBadRequest) - - return c.JSON(response.StatusCode(), response) - } - - response.Data.Spec = fmt.Sprintf("# pave spec for agecem-org %s\n", version.Version()) - - for _, route := range h.Pave.SortedRouteStrings() { - response.Data.Spec = fmt.Sprintf("%s%s", response.Data.Spec, route) - } - - response.Message = "ok" - response.SetStatusCode(http.StatusOK) - - return c.JSON(response.StatusCode(), response) -} diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..abca06b --- /dev/null +++ b/handler.go @@ -0,0 +1,806 @@ +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "sort" + + "codeberg.org/vlbeaudoin/pave/v2" + "codeberg.org/vlbeaudoin/voki/v3" + "git.agecem.com/agecem/agecem-org/api" + "git.agecem.com/agecem/agecem-org/config" + "git.agecem.com/agecem/agecem-org/media" + "git.agecem.com/agecem/agecem-org/models" + "git.agecem.com/agecem/agecem-org/version" + "github.com/labstack/echo/v4" + "github.com/minio/minio-go/v7" +) + +type V1Handler struct { + Config config.Config + MediaClient *media.MediaClient + Pave *pave.Pave +} + +// API Handlers + +// ListRoutes affiche les routes accessibles. +// Les routes sont triées selon .Path, pour les rendre plus facilement navigables. +func (h *V1Handler) ListRoutes(c echo.Context) error { + routes := c.Echo().Routes() + sort.Slice(routes, func(i, j int) bool { return routes[i].Path < routes[j].Path }) + return c.JSON(http.StatusOK, routes) +} + +// ListBuckets affiche les buckets permis par server.documents.buckets, qui existent. +func (h *V1Handler) ListBuckets(c echo.Context) error { + var request ListBucketsRequest + var response ListBucketsResponse + + if !request.Complete() { + response.Message = "Incomplete ListBuckets request received" + response.SetStatusCode(http.StatusBadRequest) + + return c.JSON(response.StatusCode(), response) + } + + var buckets = make(map[string]string) + + for bucket_name, bucket_display_name := range h.Config.Server.Documents.Buckets { + exists, err := h.MediaClient.MinioClient.BucketExists(context.Background(), bucket_name) + if err != nil { + response.Message = "Error during minio#BucketExists" + response.SetStatusCode(http.StatusInternalServerError) + + return c.JSON(response.StatusCode(), response) + } + + if exists { + buckets[bucket_name] = bucket_display_name + } + } + + response.SetStatusCode(http.StatusOK) + response.Message = "Buckets list successful" + response.Data.Buckets = buckets + + return c.JSON(response.StatusCode(), response) +} + +func (h *V1Handler) ReadBucket(c echo.Context) error { + var request ReadBucketRequest + var response ReadBucketResponse + + request.Params.Bucket = c.Param("bucket") + + if !request.Complete() { + response.Message = "Incomplete ReadBucket request received" + response.SetStatusCode(http.StatusBadRequest) + + return c.JSON(response.StatusCode(), response) + } + + allowed := false + for bucket_allowed := range h.Config.Server.Documents.Buckets { + if request.Params.Bucket == bucket_allowed { + allowed = true + } + } + + if !allowed { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + exists, err := h.MediaClient.MinioClient.BucketExists(ctx, request.Params.Bucket) + if err != nil { + response.Message = "Error during minio#BucketExists" + response.SetStatusCode(http.StatusInternalServerError) + response.Error = err.Error() + + return c.JSON(response.StatusCode(), response) + } + + if !exists { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + objectCh := h.MediaClient.MinioClient.ListObjects(ctx, request.Params.Bucket, minio.ListObjectsOptions{}) + for object := range objectCh { + if object.Err != nil { + response.Message = "Error during minio#ListObjects" + response.SetStatusCode(http.StatusInternalServerError) + //TODO make sure this is safe + //response.Error = object.Err.Error() + + return c.JSON(response.StatusCode(), response) + } + + response.Data.Keys = append(response.Data.Keys, object.Key) + } + + response.Message = "V1BucketRead ok" + response.SetStatusCode(http.StatusOK) + + return c.JSON(response.StatusCode(), response) +} + +/* +CreateDocuments permet d'ajouter un object dans un bucket, par multipart/form-data + +Example: + +Téléverser plusieurs fichiers à cette route avec `curl`: + + curl -F 'documents=@example.pdf' -F 'documents=@example.md;type=text/markdown' +*/ +func (h *V1Handler) CreateDocuments(c echo.Context) (err error) { + var request CreateDocumentsRequest + var response CreateDocumentsResponse + + request.Params.Bucket = c.Param("bucket") + + var allowed bool + for allowedBucket := range h.Config.Server.Documents.Buckets { + if request.Params.Bucket == allowedBucket { + allowed = true + } + } + + if !allowed { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + form, err := c.MultipartForm() + if err != nil { + response.Message = fmt.Sprintf("Téléversement invalide: %s", err) + response.SetStatusCode(http.StatusBadRequest) + + return c.JSON(response.StatusCode(), response) + } + if form == nil { + response.Message = "MultipartForm pointe vers une addresse mémoire nil" + response.SetStatusCode(http.StatusBadRequest) + + return c.JSON(response.StatusCode(), response) + } + + if len(form.File) == 0 { + response.Message = "Veuillez sélectionner au moins 1 document à téléverser" + response.SetStatusCode(http.StatusBadRequest) + + return c.JSON(response.StatusCode(), response) + } + + for inputName, inputFileHeaders := range form.File { + if inputName == "documents" { + request.Data.Documents = inputFileHeaders + } + } + + if request.Data.Documents == nil { + response.Message = "Impossible d'obtenir les documents depuis le formulaire" + response.SetStatusCode(http.StatusBadRequest) + + return c.JSON(response.StatusCode(), response) + } + + if !request.Complete() { + response.Message = "Requête CreateDocuments incomplète reçue" + response.SetStatusCode(http.StatusBadRequest) + + return c.JSON(response.StatusCode(), response) + } + + var code int + code, response.Message = h.MediaClient.UploadFormFiles(request.Params.Bucket, request.Data.Documents) + response.SetStatusCode(code) + + return c.JSON(response.StatusCode(), response) +} + +// CreateDocument permet d'ajouter un object dans un bucket, par multipart/form-data +func (h *V1Handler) CreateDocument(c echo.Context) (err error) { + var request CreateDocumentRequest + var response CreateDocumentResponse + + request.Params.Bucket = c.Param("bucket") + + request.Data.Document, err = c.FormFile("document") + if err != nil { + response.SetStatusCode(http.StatusBadRequest) + response.Message = "Error during CreateDocument's echo#Context.FormFile" + response.Error = err.Error() + + return c.JSON(response.StatusCode(), response) + } + + allowed := false + for bucket_allowed := range h.Config.Server.Documents.Buckets { + if request.Params.Bucket == bucket_allowed { + allowed = true + } + } + + if !allowed { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + if !request.Complete() { + response.Message = "Incomplete CreateDocument request received" + response.SetStatusCode(http.StatusBadRequest) + + return c.JSON(response.StatusCode(), response) + } + + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + src, err := request.Data.Document.Open() + if err != nil { + response.SetStatusCode(http.StatusBadRequest) + response.Message = "Error during form_file.Open()" + response.Error = err.Error() + + return c.JSON(response.StatusCode(), response) + } + defer src.Close() + + info, err := h.MediaClient.MinioClient.PutObject(ctx, request.Params.Bucket, request.Data.Document.Filename, src, request.Data.Document.Size, minio.PutObjectOptions{ + ContentType: request.Data.Document.Header.Get("Content-Type"), + }) + if err != nil { + response.SetStatusCode(http.StatusInternalServerError) + response.Message = "Error during minio#PutObject" + //response.Error = err.Error() + + return c.JSON(response.StatusCode(), response) + } + + response.SetStatusCode(http.StatusOK) + response.Message = "ok" + response.Data.Bucket = info.Bucket + response.Data.Key = info.Key + response.Data.Size = info.Size + + return c.JSON(response.StatusCode(), response) +} + +// ReadDocument permet de lire le contenu d'un fichier et protentiellement de le télécharger +func (h *V1Handler) ReadDocument(c echo.Context) error { + bucket := c.Param("bucket") + document := c.Param("document") + + allowed := false + for bucket_allowed := range h.Config.Server.Documents.Buckets { + if bucket == bucket_allowed { + allowed = true + } + } + + if !allowed { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + bucket_exists, err := h.MediaClient.MinioClient.BucketExists(ctx, bucket) + if err != nil { + return c.JSON(http.StatusInternalServerError, "Error during minio#BucketExists") + } + + if !bucket_exists { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + document_info, err := h.MediaClient.MinioClient.StatObject(ctx, bucket, document, minio.StatObjectOptions{}) + + if err != nil { + if err.Error() == "The specified key does not exist." { + + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + return c.JSON(http.StatusInternalServerError, map[string]interface{}{ + "message": "Error during minio#StatObject", + }) + } + + _ = document_info + + document_object, err := h.MediaClient.MinioClient.GetObject(ctx, bucket, document, minio.GetObjectOptions{}) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during minio#GetObject", + }) + } + + defer document_object.Close() + + return c.Stream(http.StatusOK, document_info.ContentType, document_object) +} + +// DeleteDocument permet de supprimer un object +func (h *V1Handler) DeleteDocument(c echo.Context) error { + var request DeleteDocumentRequest + var response DeleteDocumentResponse + + request.Params.Bucket = c.Param("bucket") + request.Params.Document = c.Param("document") + + allowed := false + for bucket_allowed := range h.Config.Server.Documents.Buckets { + if request.Params.Bucket == bucket_allowed { + allowed = true + } + } + + if !allowed { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + if !request.Complete() { + response.Message = "Incomplete DeleteDocument request received" + response.SetStatusCode(http.StatusBadRequest) + + return c.JSON(response.StatusCode(), response) + } + + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + bucket_exists, err := h.MediaClient.MinioClient.BucketExists(ctx, request.Params.Bucket) + if err != nil { + return c.JSON(http.StatusInternalServerError, "Error during minio#BucketExists") + } + + if !bucket_exists { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + document_info, err := h.MediaClient.MinioClient.StatObject(ctx, request.Params.Bucket, request.Params.Document, minio.StatObjectOptions{}) + if err != nil { + if err.Error() == "The specified key does not exist." { + + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + //response.Error = err.Error() + response.Message = "Error during minio#StatObject" + response.SetStatusCode(http.StatusInternalServerError) + + return c.JSON(response.StatusCode(), response) + } + + //TODO Add error validation + _ = document_info + + err = h.MediaClient.MinioClient.RemoveObject(ctx, request.Params.Bucket, request.Params.Document, minio.RemoveObjectOptions{}) + if err != nil { + //response.Error = err.Error() + response.Message = "Error during minio#RemoveObject" + response.SetStatusCode(http.StatusInternalServerError) + + return c.JSON(response.StatusCode(), response) + } + + response.Message = "Document deleted" + response.SetStatusCode(http.StatusOK) + + return c.JSON(response.StatusCode(), response) +} + +// UpdateDocumentKey +func (h *V1Handler) UpdateDocumentKey(c echo.Context) (err error) { + var request UpdateDocumentKeyRequest + var response UpdateDocumentKeyResponse + + request.Params.Bucket = c.Param("bucket") + request.Params.Document = c.Param("document") + + var newKey string + err = c.Bind(&newKey) + if err != nil { + response.SetStatusCode(http.StatusBadRequest) + response.Message = err.Error() + + return c.JSON(response.StatusCode(), response) + } + request.Data.NewKey = newKey + + if !request.Complete() { + response.SetStatusCode(http.StatusBadRequest) + response.Message = "Incomplete UpdateDocumentKey request received" + + return c.JSON(response.StatusCode(), response) + } + + var allowed bool + for bucketAllowed := range h.Config.Server.Documents.Buckets { + if request.Params.Bucket == bucketAllowed { + allowed = true + } + } + + if !allowed { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + bucketExists, err := h.MediaClient.MinioClient.BucketExists(ctx, request.Params.Bucket) + if err != nil { + return c.JSON(http.StatusInternalServerError, "Could not validate bucket exists") + } + + if !bucketExists { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + // Check source object exists + + if _, err := h.MediaClient.MinioClient.StatObject(ctx, request.Params.Bucket, request.Params.Document, minio.StatObjectOptions{}); err != nil { + if err.Error() == "The specified key does not exist." { + + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + + response.Message = fmt.Sprintf("Could not obtain information on %s/%s", request.Params.Bucket, request.Params.Document) + response.SetStatusCode(http.StatusInternalServerError) + + return c.JSON(response.StatusCode(), response) + } + + // Copy object to newKey + + if _, err := h.MediaClient.MinioClient.CopyObject(ctx, + minio.CopyDestOptions{Bucket: request.Params.Bucket, Object: request.Data.NewKey}, + minio.CopySrcOptions{Bucket: request.Params.Bucket, Object: request.Params.Document}, + ); err != nil { + response.SetStatusCode(http.StatusInternalServerError) + response.Message = "Impossible de copier un document pour le renommer" + return c.JSON(response.StatusCode(), response) + } + + // Verify copy was successful + if _, err := h.MediaClient.MinioClient.StatObject(ctx, + request.Params.Bucket, request.Data.NewKey, minio.GetObjectOptions{}); err != nil { + response.SetStatusCode(http.StatusInternalServerError) + response.Message = "Copie de document ne semble pas avoir fonctionnée" + return c.JSON(response.StatusCode(), response) + } + + // Delete old file + if err := h.MediaClient.MinioClient.RemoveObject(ctx, + request.Params.Bucket, request.Params.Document, minio.RemoveObjectOptions{}); err != nil { + response.SetStatusCode(http.StatusInternalServerError) + response.Message = "Erreur pendant tentative de supprimer document source" + return c.JSON(response.StatusCode(), response) + } + + //TODO cleanup + + // Return result + response.SetStatusCode(http.StatusOK) + response.Message = "Document renommé avec succès" + response.Data.Bucket = request.Params.Bucket + response.Data.Key = request.Data.NewKey + return c.JSON(response.StatusCode(), response) + +} + +// ExecuteSeed créé des buckets dans minio selon la liste de buckets dans server.documents.buckets +// Les buckets sont créés avec paramètres par défaut, et sont ensuite visible dans /v1/bucket. +func (h *V1Handler) ExecuteSeed(c echo.Context) error { + //var request ExecuteSeedRequest + var response ExecuteSeedResponse + + new_buckets, err := h.MediaClient.Seed() + response.Data.Buckets = new_buckets + if err != nil { + response.SetStatusCode(http.StatusInternalServerError) + response.Message = "Error during mediaClient.Seed()" + response.Error = err.Error() + + return c.JSON(response.StatusCode(), response) + } + + if len(new_buckets) == 0 { + response.Message = "All buckets already exist" + + } else { + response.Message = "Buckets successfully created" + } + + response.SetStatusCode(http.StatusOK) + + return c.JSON(response.StatusCode(), response) +} + +const DescriptionV1SpecGET string = "Afficher le API spec en format pave" + +func (h *V1Handler) ReadSpec(c echo.Context) error { + var request ReadSpecRequest + var response ReadSpecResponse + + if !request.Complete() { + response.Message = "Incomplete ReadSpec request received" + response.SetStatusCode(http.StatusBadRequest) + + return c.JSON(response.StatusCode(), response) + } + + response.Data.Spec = fmt.Sprintf("# pave spec for agecem-org %s\n", version.Version()) + + for _, route := range h.Pave.SortedRouteStrings() { + response.Data.Spec = fmt.Sprintf("%s%s", response.Data.Spec, route) + } + + response.Message = "ok" + response.SetStatusCode(http.StatusOK) + + return c.JSON(response.StatusCode(), response) +} + +type WebHandler struct { + ApiClient *api.API +} + +func HandleIndex(c echo.Context) error { + return c.Render(http.StatusOK, "index-html", nil) +} + +/* +func HandleAPropos(c echo.Context) error { + return c.Render(http.StatusOK, "a-propos-html", nil) +} +*/ + +/* +func HandleActualite(c echo.Context) error { + return c.Render(http.StatusOK, "actualite-html", nil) +} +*/ + +/* +func HandleActualiteArticle(c echo.Context) error { + article := c.Param("article") + return c.String(http.StatusOK, fmt.Sprintf("Article: %s", article)) +} +*/ + +func HandleVieEtudiante(c echo.Context) error { + return c.Render(http.StatusOK, "vie-etudiante-html", nil) +} + +func HandleVieEtudianteOrganisme(c echo.Context) error { + organisme := c.Param("organisme") + return c.String(http.StatusOK, fmt.Sprintf("Organisme: %s", organisme)) +} + +func (h *WebHandler) HandleDocumentation(c echo.Context) error { + var response HandleDocumentationResponse + + v1BucketsGET, err := h.ApiClient.ListBuckets() + if err != nil { + response.Error = err.Error() + response.Message = v1BucketsGET.Message + response.SetStatusCode(v1BucketsGET.StatusCode()) + + return c.Render(response.StatusCode(), "documentation-html", response) + } + + //TODO check v1BucketsGET StatusCode and Error + + for bucket, displayName := range v1BucketsGET.Data.Buckets { + // TODO move call to dedicated API client method + var v1BucketReadResponse ReadBucketResponse + + if err = h.ApiClient.Voki.Unmarshal(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", bucket), nil, true, &v1BucketReadResponse); err != nil { + response.Error = err.Error() + response.Message = "Error during json.Unmarshal /v1/bucket/:bucket" + response.SetStatusCode(http.StatusInternalServerError) + + return c.Render(http.StatusOK, "documentation-html", response) + } + + response.Data.Buckets = append(response.Data.Buckets, models.Bucket{ + Name: bucket, + DisplayName: displayName, + Documents: v1BucketReadResponse.Data.Keys, + }) + } + + sort.SliceStable(response.Data.Buckets, func(i, j int) bool { return response.Data.Buckets[i].Name < response.Data.Buckets[j].Name }) + + response.SetStatusCode(http.StatusOK) + //response.Message = "HandleDocumentation ok" + + // TODO render .Message + return c.Render(http.StatusOK, "documentation-html", response) + //return c.Render(response.StatusCode(), "documentation-html", response.Data.Buckets) +} + +func HandleFormulaires(c echo.Context) error { + return c.Render(http.StatusOK, "formulaires-html", nil) +} + +func (h *WebHandler) HandlePublicDocumentation(c echo.Context) error { + bucket := c.Param("bucket") + document := c.Param("document") + + unescaped, err := url.QueryUnescape(fmt.Sprintf("/v1/bucket/%s/%s", bucket, document)) + if err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"message": "Bad Request"}) + } + + response, err := h.ApiClient.Voki.Call(http.MethodGet, unescaped, nil, true) + if err != nil { + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + } + defer response.Body.Close() + + switch response.StatusCode { + case http.StatusNotFound: + response := voki.ResponseNotFound{} + return c.JSON(response.StatusCode(), response) + case http.StatusInternalServerError: + return c.JSON(http.StatusInternalServerError, map[string]string{"message": "Internal Server Error"}) + } + + body, err := io.ReadAll(response.Body) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{"message": "Internal Server Error"}) + } + + return c.Blob(http.StatusOK, "application/octet-stream", body) +} + +func HandleAdmin(c echo.Context) error { + return c.Render(http.StatusOK, "admin-html", nil) +} + +func (h *WebHandler) HandleAdminDocumentsUpload(c echo.Context) error { + var response HandleAdminDocumentsUploadResponse + + v1BucketsGET, err := h.ApiClient.ListBuckets() + if err != nil { + response.SetStatusCode(v1BucketsGET.StatusCode()) + response.Error = err.Error() + response.Message = v1BucketsGET.Message + + return c.Render(response.StatusCode(), "admin-upload-html", nil) + } + + for bucketName, displayName := range v1BucketsGET.Data.Buckets { + response.Data.Buckets = append(response.Data.Buckets, models.Bucket{ + Name: bucketName, + DisplayName: displayName, + }) + } + response.SetStatusCode(http.StatusOK) + + return c.Render(response.StatusCode(), "admin-upload-html", response) +} + +func (h *WebHandler) HandleAdminDocumentsUploadPOST(c echo.Context) error { + var request CreateDocumentsRequest + var response HandleAdminDocumentsUploadResponse + + v1BucketsGET, err := h.ApiClient.ListBuckets() + if err != nil { + response.SetStatusCode(v1BucketsGET.StatusCode()) + response.Message = v1BucketsGET.Message + response.Error = err.Error() + + return c.Render(response.StatusCode(), "admin-upload-html", response) + } + + for bucketName, displayName := range v1BucketsGET.Data.Buckets { + response.Data.Buckets = append(response.Data.Buckets, models.Bucket{ + Name: bucketName, + DisplayName: displayName, + }) + } + + request.Params.Bucket = c.FormValue("bucket") + + form, err := c.MultipartForm() + if err != nil { + response.SetStatusCode(http.StatusBadRequest) + response.Message = "Impossible de téléverser" + response.Error = err.Error() + + return c.Render(response.StatusCode(), "admin-upload-html", response) + } + if form == nil { + response.SetStatusCode(http.StatusInternalServerError) + response.Message = "Formulaire pointe vers une addresse mémoire nulle" + response.Error = "Formulaire pointe vers une addresse mémoire nulle" + + return c.Render(response.StatusCode(), "admin-upload-html", response) + } + + if len(form.File) == 0 { + response.SetStatusCode(http.StatusBadRequest) + response.Message = "Veuillez sélectionner au moins 1 fichier à téléverser" + response.Error = "Input 'documents' ne contient aucun fichier" + + return c.Render(response.StatusCode(), "admin-upload-html", response) + } + + for inputName, inputFileHeaders := range form.File { + if inputName == "documents" { + request.Data.Documents = inputFileHeaders + break + } + } + + if request.Data.Documents == nil { + response.SetStatusCode(http.StatusBadRequest) + response.Message = "Impossible d'obtenir les documents depuis le formulaire" + response.Error = "Impossible d'obtenir les documents depuis le formulaire" + + return c.Render(response.StatusCode(), "admin-upload-html", response) + } + + uploadDocumentsResponse, err := h.ApiClient.UploadDocuments(request.Params.Bucket, request.Data.Documents...) + if err != nil { + //TODO figure out pourquoi `err` n'est jamais `nil` + response.SetStatusCode(uploadDocumentsResponse.StatusCode()) + response.Message = uploadDocumentsResponse.Message + response.Error = fmt.Sprintf("%s. Détails: %s", err.Error(), uploadDocumentsResponse.Error) + /* + response.SetStatusCode(http.StatusInternalServerError) + response.Message = fmt.Sprintf("api.(*API).UploadDocuments: %s", err) + response.Error = err.Error() + */ + + return c.Render(response.StatusCode(), "admin-upload-html", response) + } + + //TODO figure out pourquoi on se rend jamais ici + + // Format response + var info, status string + + for i, document := range uploadDocumentsResponse.Data.Documents { + info = fmt.Sprintf("%s[%d] /public/documentation/%s/%s (%dk) ok\n", + info, i, uploadDocumentsResponse.Data.Bucket, document.Key, document.Size) + } + + status = uploadDocumentsResponse.Message + if errMsg := uploadDocumentsResponse.Error; errMsg != "" { + status = fmt.Sprintf("%s. Erreur: %s", status, errMsg) + } + + response.SetStatusCode(http.StatusOK) + response.Message = fmt.Sprintf("%s \n %s", status, info) + + return c.Render(response.StatusCode(), "admin-upload-html", response) +} diff --git a/webhandler/webhandler.go b/webhandler/webhandler.go deleted file mode 100644 index 082aff2..0000000 --- a/webhandler/webhandler.go +++ /dev/null @@ -1,254 +0,0 @@ -package webhandler - -import ( - "fmt" - "io" - "net/http" - "net/url" - "sort" - - "codeberg.org/vlbeaudoin/voki/v3" - "git.agecem.com/agecem/agecem-org/api" - "git.agecem.com/agecem/agecem-org/apirequest" - "git.agecem.com/agecem/agecem-org/models" - "github.com/labstack/echo/v4" -) - -type WebHandler struct { - ApiClient *api.API -} - -func HandleIndex(c echo.Context) error { - return c.Render(http.StatusOK, "index-html", nil) -} - -/* -func HandleAPropos(c echo.Context) error { - return c.Render(http.StatusOK, "a-propos-html", nil) -} -*/ - -/* -func HandleActualite(c echo.Context) error { - return c.Render(http.StatusOK, "actualite-html", nil) -} -*/ - -/* -func HandleActualiteArticle(c echo.Context) error { - article := c.Param("article") - return c.String(http.StatusOK, fmt.Sprintf("Article: %s", article)) -} -*/ - -func HandleVieEtudiante(c echo.Context) error { - return c.Render(http.StatusOK, "vie-etudiante-html", nil) -} - -func HandleVieEtudianteOrganisme(c echo.Context) error { - organisme := c.Param("organisme") - return c.String(http.StatusOK, fmt.Sprintf("Organisme: %s", organisme)) -} - -func (h *WebHandler) HandleDocumentation(c echo.Context) error { - var response HandleDocumentationResponse - - v1BucketsGET, err := h.ApiClient.ListBuckets() - if err != nil { - response.Error = err.Error() - response.Message = v1BucketsGET.Message - response.SetStatusCode(v1BucketsGET.StatusCode()) - - return c.Render(response.StatusCode(), "documentation-html", response) - } - - //TODO check v1BucketsGET StatusCode and Error - - for bucket, displayName := range v1BucketsGET.Data.Buckets { - // TODO move call to dedicated API client method - var v1BucketReadResponse V1BucketGET - - if err = h.ApiClient.Voki.Unmarshal(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", bucket), nil, true, &v1BucketReadResponse); err != nil { - response.Error = err.Error() - response.Message = "Error during json.Unmarshal /v1/bucket/:bucket" - response.SetStatusCode(http.StatusInternalServerError) - - return c.Render(http.StatusOK, "documentation-html", response) - } - - response.Data.Buckets = append(response.Data.Buckets, models.Bucket{ - Name: bucket, - DisplayName: displayName, - Documents: v1BucketReadResponse.Data.Keys, - }) - } - - sort.SliceStable(response.Data.Buckets, func(i, j int) bool { return response.Data.Buckets[i].Name < response.Data.Buckets[j].Name }) - - response.SetStatusCode(http.StatusOK) - //response.Message = "HandleDocumentation ok" - - // TODO render .Message - return c.Render(http.StatusOK, "documentation-html", response) - //return c.Render(response.StatusCode(), "documentation-html", response.Data.Buckets) -} - -func HandleFormulaires(c echo.Context) error { - return c.Render(http.StatusOK, "formulaires-html", nil) -} - -func (h *WebHandler) HandlePublicDocumentation(c echo.Context) error { - bucket := c.Param("bucket") - document := c.Param("document") - - unescaped, err := url.QueryUnescape(fmt.Sprintf("/v1/bucket/%s/%s", bucket, document)) - if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "Bad Request"}) - } - - response, err := h.ApiClient.Voki.Call(http.MethodGet, unescaped, nil, true) - if err != nil { - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - } - defer response.Body.Close() - - switch response.StatusCode { - case http.StatusNotFound: - response := voki.ResponseNotFound{} - return c.JSON(response.StatusCode(), response) - case http.StatusInternalServerError: - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "Internal Server Error"}) - } - - body, err := io.ReadAll(response.Body) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "Internal Server Error"}) - } - - return c.Blob(http.StatusOK, "application/octet-stream", body) -} - -func HandleAdmin(c echo.Context) error { - return c.Render(http.StatusOK, "admin-html", nil) -} - -func (h *WebHandler) HandleAdminDocumentsUpload(c echo.Context) error { - var response HandleAdminDocumentsUploadResponse - - v1BucketsGET, err := h.ApiClient.ListBuckets() - if err != nil { - response.SetStatusCode(v1BucketsGET.StatusCode()) - response.Error = err.Error() - response.Message = v1BucketsGET.Message - - return c.Render(response.StatusCode(), "admin-upload-html", nil) - } - - for bucketName, displayName := range v1BucketsGET.Data.Buckets { - response.Data.Buckets = append(response.Data.Buckets, models.Bucket{ - Name: bucketName, - DisplayName: displayName, - }) - } - response.SetStatusCode(http.StatusOK) - - return c.Render(response.StatusCode(), "admin-upload-html", response) -} - -func (h *WebHandler) HandleAdminDocumentsUploadPOST(c echo.Context) error { - var request apirequest.CreateDocumentsResponse - var response HandleAdminDocumentsUploadResponse - - v1BucketsGET, err := h.ApiClient.ListBuckets() - if err != nil { - response.SetStatusCode(v1BucketsGET.StatusCode()) - response.Message = v1BucketsGET.Message - response.Error = err.Error() - - return c.Render(response.StatusCode(), "admin-upload-html", response) - } - - for bucketName, displayName := range v1BucketsGET.Data.Buckets { - response.Data.Buckets = append(response.Data.Buckets, models.Bucket{ - Name: bucketName, - DisplayName: displayName, - }) - } - - request.Params.Bucket = c.FormValue("bucket") - - form, err := c.MultipartForm() - if err != nil { - response.SetStatusCode(http.StatusBadRequest) - response.Message = "Impossible de téléverser" - response.Error = err.Error() - - return c.Render(response.StatusCode(), "admin-upload-html", response) - } - if form == nil { - response.SetStatusCode(http.StatusInternalServerError) - response.Message = "Formulaire pointe vers une addresse mémoire nulle" - response.Error = "Formulaire pointe vers une addresse mémoire nulle" - - return c.Render(response.StatusCode(), "admin-upload-html", response) - } - - if len(form.File) == 0 { - response.SetStatusCode(http.StatusBadRequest) - response.Message = "Veuillez sélectionner au moins 1 fichier à téléverser" - response.Error = "Input 'documents' ne contient aucun fichier" - - return c.Render(response.StatusCode(), "admin-upload-html", response) - } - - for inputName, inputFileHeaders := range form.File { - if inputName == "documents" { - request.Data.Documents = inputFileHeaders - break - } - } - - if request.Data.Documents == nil { - response.SetStatusCode(http.StatusBadRequest) - response.Message = "Impossible d'obtenir les documents depuis le formulaire" - response.Error = "Impossible d'obtenir les documents depuis le formulaire" - - return c.Render(response.StatusCode(), "admin-upload-html", response) - } - - uploadDocumentsResponse, err := h.ApiClient.UploadDocuments(request.Params.Bucket, request.Data.Documents...) - if err != nil { - //TODO figure out pourquoi `err` n'est jamais `nil` - response.SetStatusCode(uploadDocumentsResponse.StatusCode()) - response.Message = uploadDocumentsResponse.Message - response.Error = fmt.Sprintf("%s. Détails: %s", err.Error(), uploadDocumentsResponse.Error) - /* - response.SetStatusCode(http.StatusInternalServerError) - response.Message = fmt.Sprintf("api.(*API).UploadDocuments: %s", err) - response.Error = err.Error() - */ - - return c.Render(response.StatusCode(), "admin-upload-html", response) - } - - //TODO figure out pourquoi on se rend jamais ici - - // Format response - var info, status string - - for i, document := range uploadDocumentsResponse.Data.Documents { - info = fmt.Sprintf("%s[%d] /public/documentation/%s/%s (%dk) ok\n", - info, i, uploadDocumentsResponse.Data.Bucket, document.Key, document.Size) - } - - status = uploadDocumentsResponse.Message - if errMsg := uploadDocumentsResponse.Error; errMsg != "" { - status = fmt.Sprintf("%s. Erreur: %s", status, errMsg) - } - - response.SetStatusCode(http.StatusOK) - response.Message = fmt.Sprintf("%s \n %s", status, info) - - return c.Render(response.StatusCode(), "admin-upload-html", response) -} From a61058c22a1af9f3d093820d1657b3443abfa889 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:12:56 -0400 Subject: [PATCH 06/15] merge: config dans config.go --- config/config.go => config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename config/config.go => config.go (98%) diff --git a/config/config.go b/config.go similarity index 98% rename from config/config.go rename to config.go index 26f1a95..f426dde 100644 --- a/config/config.go +++ b/config.go @@ -1,4 +1,4 @@ -package config +package main /* Permet de contenir la configuration obtenue par cobra/viper From 451ccd1852435085e063a3c195851836e72aa57b Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:22:11 -0400 Subject: [PATCH 07/15] merge: cmd dans cmd.go Aussi fix example dans config.go --- cmd/server.go => cmd.go | 177 ++++++++++++++++++++++++++++++---------- cmd/config.go | 44 ---------- cmd/root.go | 63 -------------- cmd/version.go | 21 ----- config.go | 2 +- 5 files changed, 135 insertions(+), 172 deletions(-) rename cmd/server.go => cmd.go (60%) delete mode 100644 cmd/config.go delete mode 100644 cmd/root.go delete mode 100644 cmd/version.go diff --git a/cmd/server.go b/cmd.go similarity index 60% rename from cmd/server.go rename to cmd.go index faf6dba..f627dbf 100644 --- a/cmd/server.go +++ b/cmd.go @@ -1,41 +1,118 @@ /* -Copyright © 2023 AGECEM +Copyright © 2023-2024 AGECEM */ -package cmd +package main import ( "crypto/subtle" - "fmt" - "log" - "embed" - "html/template" + "encoding/json" + "fmt" "io" + "log" "net/http" + "os" + "strings" + "text/template" "codeberg.org/vlbeaudoin/pave/v2" "codeberg.org/vlbeaudoin/serpents" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "git.agecem.com/agecem/agecem-org/api" - "git.agecem.com/agecem/agecem-org/apihandler" - "git.agecem.com/agecem/agecem-org/apirequest" - "git.agecem.com/agecem/agecem-org/apiresponse" - "git.agecem.com/agecem/agecem-org/config" "git.agecem.com/agecem/agecem-org/media" "git.agecem.com/agecem/agecem-org/public" "git.agecem.com/agecem/agecem-org/templates" - "git.agecem.com/agecem/agecem-org/webhandler" + "git.agecem.com/agecem/agecem-org/version" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "github.com/spf13/cobra" + "github.com/spf13/viper" ) +// configCmd represents the config command +var configCmd = &cobra.Command{ + Use: "config", + Short: "Print the config to stdout in indented JSON format", + Run: func(cmd *cobra.Command, args []string) { + var cfg Config + + if err := viper.Unmarshal(&cfg); err != nil { + log.Fatal(err) + } + + printConfig(cfg) + }, +} + +func init() { + rootCmd.AddCommand(configCmd) +} + +func printConfig(config Config) error { + buf, err := json.MarshalIndent(config, "", " ") + if err != nil { + return err + } + + fmt.Println(string(buf)) + + return nil +} + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "agecem-org", + Short: "Application du site web de l'AGECEM", + Long: "Application du site web de l'AGECEM, l'Association Générale Étudiante du Cégep Édouard-Montpetit.", +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.agecem-org.yaml)") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".agecem-org" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".agecem-org") + } + + viper.SetEnvPrefix("AGECEM_ORG") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} + type Template struct { templates *template.Template } -var cfg config.Config +var cfg Config var ( publicFS embed.FS @@ -210,62 +287,63 @@ func RunServer() { p := pave.New() - v1Handler := apihandler.V1Handler{ + v1Handler := V1Handler{ Config: cfg, MediaClient: mediaClient, Pave: &p, } - groupV1.GET("", v1Handler.V1GET) + groupV1.GET("", v1Handler.ListRoutes) if err := pave.EchoRegister[ - apirequest.V1SeedPOST, - apiresponse.V1SeedPOST](groupV1, &p, "/v1", http.MethodPost, "/seed", "Créer buckets manquants définis dans `server.documents.buckets`", "V1SeedPOST", v1Handler.V1SeedPOST); err != nil { + ExecuteSeedRequest, + ExecuteSeedResponse](groupV1, &p, "/v1", http.MethodPost, "/seed", "Créer buckets manquants définis dans `server.documents.buckets`", "ExecuteSeed", v1Handler.ExecuteSeed); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ - apirequest.V1DocumentKeyPUT, - apiresponse.V1DocumentKeyPUT](groupV1, &p, "/v1", http.MethodPut, "/bucket/:bucket/:document/key", "Renommer un document", "V1DocumentKeyPUT", v1Handler.V1DocumentKeyPUT); err != nil { + UpdateDocumentKeyRequest, + UpdateDocumentKeyResponse](groupV1, &p, "/v1", http.MethodPut, "/bucket/:bucket/:document/key", "Renommer un document", "UpdateDocumentKey", v1Handler.UpdateDocumentKey); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ - apirequest.V1SpecGET, - apiresponse.V1SpecGET](groupV1, &p, "/v1", http.MethodGet, "/spec", apihandler.DescriptionV1SpecGET, "V1SpecGET", v1Handler.V1SpecGET); err != nil { + ReadSpecRequest, + ReadSpecResponse](groupV1, &p, "/v1", http.MethodGet, "/spec", DescriptionV1SpecGET, "SpecRead", v1Handler.ReadSpec); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ - apirequest.V1BucketsGET, - apiresponse.V1BucketsGET](groupV1, &p, "/v1", http.MethodGet, "/bucket", "List buckets", "V1BucketsGET", v1Handler.V1BucketsGET); err != nil { + ListBucketsRequest, + ListBucketsResponse](groupV1, &p, "/v1", http.MethodGet, "/bucket", "List buckets", "ListBuckets", v1Handler.ListBuckets); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ - apirequest.V1BucketGET, - apiresponse.V1BucketGET](groupV1, &p, "/v1", http.MethodGet, "/bucket/:bucket", "Read bucket content", "V1BucketGET", v1Handler.V1BucketGET); err != nil { + ReadBucketRequest, + ReadBucketResponse](groupV1, &p, "/v1", http.MethodGet, "/bucket/:bucket", "Read bucket content", "ReadBucket", v1Handler.ReadBucket); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ - apirequest.CreateDocumentsResponse, - apiresponse.V1DocumentsPOST](groupV1, &p, "/v1", http.MethodPost, "/bucket/:bucket/many", "Upload documents to specified bucket", "V1DocumentsPOST", v1Handler.V1DocumentsPOST); err != nil { + CreateDocumentsRequest, + CreateDocumentsResponse](groupV1, &p, "/v1", http.MethodPost, "/bucket/:bucket/many", "Upload documents to specified bucket", "CreateDocuments", v1Handler.CreateDocuments); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ - apirequest.V1DocumentPOST, - apiresponse.V1DocumentPOST](groupV1, &p, "/v1", http.MethodPost, "/bucket/:bucket", "Upload document to specified bucket", "V1DocumentPOST", v1Handler.V1DocumentPOST); err != nil { + CreateDocumentRequest, + CreateDocumentResponse](groupV1, &p, "/v1", http.MethodPost, "/bucket/:bucket", "Upload document to specified bucket", "CreateDocument", v1Handler.CreateDocument); err != nil { log.Fatal(err) } - groupV1.GET("/bucket/:bucket/:document", v1Handler.V1DocumentGET) + // Do not move to pave, uses echo.Stream instead of echo.JSON + groupV1.GET("/bucket/:bucket/:document", v1Handler.ReadDocument) if err := pave.EchoRegister[ - apirequest.V1DocumentDELETE, - apiresponse.V1DocumentDELETE](groupV1, &p, "/v1", http.MethodDelete, "/bucket/:bucket/:document", "Delete document in specified bucket", "V1DocumentDELETE", v1Handler.V1DocumentDELETE); err != nil { + DeleteDocumentRequest, + DeleteDocumentResponse](groupV1, &p, "/v1", http.MethodDelete, "/bucket/:bucket/:document", "Delete document in specified bucket", "DeleteDocument", v1Handler.DeleteDocument); err != nil { log.Fatal(err) } @@ -278,25 +356,25 @@ func RunServer() { log.Fatal(err) } - webHandler := webhandler.WebHandler{ + webHandler := WebHandler{ ApiClient: apiClient, } - e.GET("/", webhandler.HandleIndex) + e.GET("/", HandleIndex) - //e.GET("/a-propos", webhandler.HandleAPropos) + //e.GET("/a-propos", HandleAPropos) - //e.GET("/actualite", webhandler.HandleActualite) + //e.GET("/actualite", HandleActualite) - //e.GET("/actualite/:article", webhandler.HandleActualiteArticle) + //e.GET("/actualite/:article", HandleActualiteArticle) - e.GET("/vie-etudiante", webhandler.HandleVieEtudiante) + e.GET("/vie-etudiante", HandleVieEtudiante) - e.GET("/vie-etudiante/:organisme", webhandler.HandleVieEtudianteOrganisme) + e.GET("/vie-etudiante/:organisme", HandleVieEtudianteOrganisme) e.GET("/documentation", webHandler.HandleDocumentation) - e.GET("/formulaires", webhandler.HandleFormulaires) + e.GET("/formulaires", HandleFormulaires) // Public Routes @@ -304,7 +382,7 @@ func RunServer() { // Admin Routes - groupAdmin.GET("", webhandler.HandleAdmin) + groupAdmin.GET("", HandleAdmin) groupAdmin.GET("/documents/upload", webHandler.HandleAdminDocumentsUpload) @@ -317,3 +395,16 @@ func RunServer() { func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { return t.templates.ExecuteTemplate(w, name, data) } + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print version registered at build time", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("agecem-org", version.Version()) + }, +} + +func init() { + rootCmd.AddCommand(versionCmd) +} diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index a8d41f0..0000000 --- a/cmd/config.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright © 2023 AGECEM -*/ -package cmd - -import ( - "encoding/json" - "fmt" - "log" - - "git.agecem.com/agecem/agecem-org/config" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// configCmd represents the config command -var configCmd = &cobra.Command{ - Use: "config", - Short: "Print the config to stdout in indented JSON format", - Run: func(cmd *cobra.Command, args []string) { - var cfg config.Config - - if err := viper.Unmarshal(&cfg); err != nil { - log.Fatal(err) - } - - printConfig(cfg) - }, -} - -func init() { - rootCmd.AddCommand(configCmd) -} - -func printConfig(config config.Config) error { - buf, err := json.MarshalIndent(config, "", " ") - if err != nil { - return err - } - - fmt.Println(string(buf)) - - return nil -} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 94917bf..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,63 +0,0 @@ -/* -Copyright © 2023 AGECEM -*/ -package cmd - -import ( - "fmt" - "os" - "strings" - - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var cfgFile string - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "agecem-org", - Short: "Application du site web de l'AGECEM", - Long: "Application du site web de l'AGECEM, l'Association Générale Étudiante du Cégep Édouard-Montpetit.", -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} - -func init() { - cobra.OnInitialize(initConfig) - - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.agecem-org.yaml)") -} - -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - // Search config in home directory with name ".agecem-org" (without extension). - viper.AddConfigPath(home) - viper.SetConfigType("yaml") - viper.SetConfigName(".agecem-org") - } - - viper.SetEnvPrefix("AGECEM_ORG") - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } -} diff --git a/cmd/version.go b/cmd/version.go deleted file mode 100644 index e2b73c2..0000000 --- a/cmd/version.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "fmt" - - "git.agecem.com/agecem/agecem-org/version" - "github.com/spf13/cobra" -) - -// versionCmd represents the version command -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print version registered at build time", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("agecem-org", version.Version()) - }, -} - -func init() { - rootCmd.AddCommand(versionCmd) -} diff --git a/config.go b/config.go index f426dde..4d80229 100644 --- a/config.go +++ b/config.go @@ -6,7 +6,7 @@ Permet de contenir la configuration obtenue par cobra/viper Example d'utilisation sans error handling: ``` -var cfg config.Config +var cfg Config viper.Unmarshal(&cfg) ``` From 103745fe76e1a6796d97d789f741b052d02a34c4 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:24:20 -0400 Subject: [PATCH 08/15] merge: models dans entity.go --- models/models.go => entity.go | 2 +- handler.go | 10 ++++------ response.go | 5 ++--- 3 files changed, 7 insertions(+), 10 deletions(-) rename models/models.go => entity.go (85%) diff --git a/models/models.go b/entity.go similarity index 85% rename from models/models.go rename to entity.go index 9ef3c9e..bc34cf5 100644 --- a/models/models.go +++ b/entity.go @@ -1,4 +1,4 @@ -package models +package main type Bucket struct { Name string diff --git a/handler.go b/handler.go index abca06b..5287d4d 100644 --- a/handler.go +++ b/handler.go @@ -11,16 +11,14 @@ import ( "codeberg.org/vlbeaudoin/pave/v2" "codeberg.org/vlbeaudoin/voki/v3" "git.agecem.com/agecem/agecem-org/api" - "git.agecem.com/agecem/agecem-org/config" "git.agecem.com/agecem/agecem-org/media" - "git.agecem.com/agecem/agecem-org/models" "git.agecem.com/agecem/agecem-org/version" "github.com/labstack/echo/v4" "github.com/minio/minio-go/v7" ) type V1Handler struct { - Config config.Config + Config Config MediaClient *media.MediaClient Pave *pave.Pave } @@ -628,7 +626,7 @@ func (h *WebHandler) HandleDocumentation(c echo.Context) error { return c.Render(http.StatusOK, "documentation-html", response) } - response.Data.Buckets = append(response.Data.Buckets, models.Bucket{ + response.Data.Buckets = append(response.Data.Buckets, Bucket{ Name: bucket, DisplayName: displayName, Documents: v1BucketReadResponse.Data.Keys, @@ -698,7 +696,7 @@ func (h *WebHandler) HandleAdminDocumentsUpload(c echo.Context) error { } for bucketName, displayName := range v1BucketsGET.Data.Buckets { - response.Data.Buckets = append(response.Data.Buckets, models.Bucket{ + response.Data.Buckets = append(response.Data.Buckets, Bucket{ Name: bucketName, DisplayName: displayName, }) @@ -722,7 +720,7 @@ func (h *WebHandler) HandleAdminDocumentsUploadPOST(c echo.Context) error { } for bucketName, displayName := range v1BucketsGET.Data.Buckets { - response.Data.Buckets = append(response.Data.Buckets, models.Bucket{ + response.Data.Buckets = append(response.Data.Buckets, Bucket{ Name: bucketName, DisplayName: displayName, }) diff --git a/response.go b/response.go index bbfed83..b2379b8 100644 --- a/response.go +++ b/response.go @@ -7,7 +7,6 @@ package main import ( "codeberg.org/vlbeaudoin/voki/v3" - "git.agecem.com/agecem/agecem-org/models" ) type APIResponse struct { @@ -89,7 +88,7 @@ type ReadSpecResponse struct { type HandleAdminDocumentsUploadResponse struct { APIResponse Data struct { - Buckets []models.Bucket + Buckets []Bucket } } @@ -97,7 +96,7 @@ type HandleAdminDocumentsUploadResponse struct { type HandleDocumentationResponse struct { APIResponse Data struct { - Buckets []models.Bucket + Buckets []Bucket } } From 9209663593dd76bfe7610f4a90aa2bd45a5fda9a Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:27:22 -0400 Subject: [PATCH 09/15] merge: apiclient dans api.go --- api/api.go => api.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) rename api/api.go => api.go (90%) diff --git a/api/api.go b/api.go similarity index 90% rename from api/api.go rename to api.go index e89e5ac..201be4d 100644 --- a/api/api.go +++ b/api.go @@ -1,4 +1,4 @@ -package api +package main import ( "bytes" @@ -10,9 +10,6 @@ import ( "net/url" "codeberg.org/vlbeaudoin/voki/v3" - "git.agecem.com/agecem/agecem-org/apirequest" - "git.agecem.com/agecem/agecem-org/apiresponse" - "git.agecem.com/agecem/agecem-org/config" "github.com/spf13/viper" ) @@ -24,7 +21,7 @@ type API struct { // provided the configuration options are managed by // https://git.agecem.com/agecem/agecem-org/config func NewFromViper(client *http.Client) (api *API, err error) { - var config config.Config + var config Config if err = viper.Unmarshal(&config); err != nil { return nil, err } @@ -36,7 +33,7 @@ func New(client *http.Client, host, key string, port int, protocol string) (*API return &API{Voki: voki.New(client, host, key, port, protocol)}, nil } -func (a *API) UploadDocuments(bucketName string, fileHeaders ...*multipart.FileHeader) (response apiresponse.V1DocumentsPOST, err error) { +func (a *API) UploadDocuments(bucketName string, fileHeaders ...*multipart.FileHeader) (response CreateDocumentsResponse, err error) { if count := len(fileHeaders); count == 0 { err = fmt.Errorf("api.(*API).UploadDocuments requiert au moins 1 fichier") return @@ -110,8 +107,7 @@ func (a *API) UploadDocuments(bucketName string, fileHeaders ...*multipart.FileH return response, json.NewDecoder(httpResponse.Body).Decode(&response) } -func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (apiresponse.V1DocumentPOST, error) { - var response apiresponse.V1DocumentPOST +func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (response CreateDocumentResponse, err error) { endpoint := fmt.Sprintf("%s://%s:%d", a.Voki.Protocol, a.Voki.Host, @@ -175,12 +171,12 @@ func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) ( return response, err } -func (a *API) ListBuckets() (response apiresponse.V1BucketsGET, err error) { +func (a *API) ListBuckets() (response ListBucketsResponse, err error) { return response, a.Voki.Unmarshal(http.MethodGet, "/v1/bucket", nil, true, &response) } -func (a *API) Seed() (response apiresponse.V1SeedPOST, err error) { - request, err := apirequest.NewV1SeedPOST() +func (a *API) Seed() (response ExecuteSeedResponse, err error) { + request, err := NewV1SeedPOST() if err != nil { return } From 7878ac7a594cb03b4bc1ded4b7d84553ff73eb8c Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:28:28 -0400 Subject: [PATCH 10/15] rename: api.go -> client.go plus clair que c'est pour le client API et non le serveur --- api.go => client.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename api.go => client.go (100%) diff --git a/api.go b/client.go similarity index 100% rename from api.go rename to client.go From 469f7c0458c688e19328edc099266c250373ea04 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:29:28 -0400 Subject: [PATCH 11/15] merge: mediaclient dans media.go --- media/media.go => media.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) rename media/media.go => media.go (98%) diff --git a/media/media.go b/media.go similarity index 98% rename from media/media.go rename to media.go index f023612..f4a8335 100644 --- a/media/media.go +++ b/media.go @@ -1,4 +1,4 @@ -package media +package main import ( "context" @@ -8,7 +8,6 @@ import ( "mime/multipart" "net/http" - "git.agecem.com/agecem/agecem-org/config" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "github.com/spf13/viper" @@ -37,7 +36,7 @@ func NewMediaClient(endpoint, accessKeyId, secretAccessKey string, useSSL bool) } func NewMediaClientFromViper() (*MediaClient, error) { - var cfg config.Config + var cfg Config if err := viper.Unmarshal(&cfg); err != nil { return nil, err } @@ -55,7 +54,7 @@ type MediaClient struct { } func (m *MediaClient) Seed() ([]string, error) { - var cfg config.Config + var cfg Config if err := viper.Unmarshal(&cfg); err != nil { return nil, err } From 230b4e5db699c3c7e7b00127d2cfd9712fb64157 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:31:39 -0400 Subject: [PATCH 12/15] rename: Constructeurs client.go pour inclure `*APIClient*` --- client.go | 8 ++++---- cmd.go | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/client.go b/client.go index 201be4d..94d073e 100644 --- a/client.go +++ b/client.go @@ -17,19 +17,19 @@ type API struct { Voki *voki.Voki } -// NewFromViper returns a pointer to a new API object, +// NewAPIClientFromViper returns a pointer to a new API object, // provided the configuration options are managed by // https://git.agecem.com/agecem/agecem-org/config -func NewFromViper(client *http.Client) (api *API, err error) { +func NewAPIClientFromViper(client *http.Client) (api *API, err error) { var config Config if err = viper.Unmarshal(&config); err != nil { return nil, err } - return New(client, config.Server.Api.Host, config.Server.Api.Key, config.Server.Api.Port, config.Server.Api.Protocol) + return NewAPIClient(client, config.Server.Api.Host, config.Server.Api.Key, config.Server.Api.Port, config.Server.Api.Protocol) } -func New(client *http.Client, host, key string, port int, protocol string) (*API, error) { +func NewAPIClient(client *http.Client, host, key string, port int, protocol string) (*API, error) { return &API{Voki: voki.New(client, host, key, port, protocol)}, nil } diff --git a/cmd.go b/cmd.go index f627dbf..0d49d7a 100644 --- a/cmd.go +++ b/cmd.go @@ -17,7 +17,6 @@ import ( "codeberg.org/vlbeaudoin/pave/v2" "codeberg.org/vlbeaudoin/serpents" - "git.agecem.com/agecem/agecem-org/api" "git.agecem.com/agecem/agecem-org/media" "git.agecem.com/agecem/agecem-org/public" "git.agecem.com/agecem/agecem-org/templates" @@ -351,7 +350,7 @@ func RunServer() { client := http.DefaultClient defer client.CloseIdleConnections() - apiClient, err := api.NewFromViper(client) + apiClient, err := NewAPIClientFromViper(client) if err != nil { log.Fatal(err) } From 1c2220337cfe4c3cb4214df3bd6cb786457e5415 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:33:15 -0400 Subject: [PATCH 13/15] fix: imports de mediaclient --- cmd.go | 7 +++---- main.go | 4 +--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/cmd.go b/cmd.go index 0d49d7a..dba7205 100644 --- a/cmd.go +++ b/cmd.go @@ -17,7 +17,6 @@ import ( "codeberg.org/vlbeaudoin/pave/v2" "codeberg.org/vlbeaudoin/serpents" - "git.agecem.com/agecem/agecem-org/media" "git.agecem.com/agecem/agecem-org/public" "git.agecem.com/agecem/agecem-org/templates" "git.agecem.com/agecem/agecem-org/version" @@ -127,10 +126,10 @@ var serverCmd = &cobra.Command{ log.Fatal(err) } - mediaClient, err := media.NewMediaClientFromViper() + mediaClient, err := NewMediaClientFromViper() switch err != nil { case true: - log.Printf("media.NewMediaClientFromViper error: %s", err) + log.Printf("NewMediaClientFromViper error: %s", err) case false: new_buckets, err := mediaClient.Seed() if err != nil { @@ -279,7 +278,7 @@ func RunServer() { } // API Routes - mediaClient, err := media.NewMediaClientFromViper() + mediaClient, err := NewMediaClientFromViper() if err != nil { log.Fatal("Error during NewMediaClientFromViper for API handlers") } diff --git a/main.go b/main.go index 51a4077..736ef31 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,5 @@ package main -import "git.agecem.com/agecem/agecem-org/cmd" - func main() { - cmd.Execute() + Execute() } From a76a907ce58815fc76632bfcf24987734a2e2170 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:33:59 -0400 Subject: [PATCH 14/15] fix: imports dans handler.go --- handler.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/handler.go b/handler.go index 5287d4d..f04aad3 100644 --- a/handler.go +++ b/handler.go @@ -10,8 +10,6 @@ import ( "codeberg.org/vlbeaudoin/pave/v2" "codeberg.org/vlbeaudoin/voki/v3" - "git.agecem.com/agecem/agecem-org/api" - "git.agecem.com/agecem/agecem-org/media" "git.agecem.com/agecem/agecem-org/version" "github.com/labstack/echo/v4" "github.com/minio/minio-go/v7" @@ -19,7 +17,7 @@ import ( type V1Handler struct { Config Config - MediaClient *media.MediaClient + MediaClient *MediaClient Pave *pave.Pave } @@ -565,7 +563,7 @@ func (h *V1Handler) ReadSpec(c echo.Context) error { } type WebHandler struct { - ApiClient *api.API + ApiClient *API } func HandleIndex(c echo.Context) error { From 4b78ab89d614c54e1f99d95fa16d8c3a92bf03d1 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 22 Aug 2024 13:36:00 -0400 Subject: [PATCH 15/15] =?UTF-8?q?fix(Dockerfile):=20copies=20de=20fichiers?= =?UTF-8?q?=20et=20dossiers=20apr=C3=A8s=20rename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index a51a2bc..aecbc06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,34 +6,14 @@ LABEL author="Victor Lacasse-Beaudoin " WORKDIR /go/src/app -COPY go.mod go.sum main.go ./ +COPY go.mod go.sum main.go client.go cmd.go config.go entity.go handler.go media.go request.go response.go ./ ADD public/ public/ -ADD cmd/ cmd/ - -ADD api/ api/ - -ADD apihandler/ apihandler/ - -ADD apirequest/ apirequest/ - -ADD apiresponse/ apiresponse/ - -ADD config/ config/ - -ADD media/ media/ - -ADD models/ models/ - ADD templates/ templates/ ADD version/ version/ -ADD webhandler/ webhandler/ - -Add webresponse/ webresponse/ - RUN CGO_ENABLED=0 go build -a -o agecem-org -ldflags="-X 'git.agecem.com/agecem/agecem-org/version.version=$agecem_org_version'" . # Alpine