diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..54548ad294ca856618a0ed3509c084a6d05545e7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2022, uAfrica Technologies (Pty) Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index e048f9274e050f594dc581e0cf68f08608c2db96..9e8fbdcdda876afe1a7754e8b8e7f85e38e0169d 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,24 @@ A set of utilities used by our Golang projects * [errors](./errors/README.md) -## Creating a new tag -When making changes, a new tag needs to be made in order to use the updated library in your project. First pull the tags, and check the latest version. - -`git pull --tags` - -`git tag` - -The output will be a list of tags. Create a new tag with the version number increased. E.g. if the last tag was `v1.2.7`, the new tag could be `v1.2.8`. Then push the tag. - -`git tag -a v1.2.8 -m "Tag v1.2.8"` - -`git push --tags` - -For your project, upgrade to the new version by running the `go get` command. - -`go get gitlab.com/uafrica/go-utils@v1.2.8` \ No newline at end of file +## Creating a new release +When making changes, a new release needs to be made in order to use the updated library in your project. + +1. First, make sure your `uafrica-tools` repository is up to date (minimum at commit `442f62f0`): +``` +git pull +make install +``` + +2. After your changes have been merged to the `main` branch of `go-utils`, run the following command which will automatically create a new tag: +``` +ua release +``` +and select project `uafrica/go-utils` + +3. For your project, upgrade to the new version by running the `go get` command and specifying the new tab: +``` +go get gitlab.com/uafrica/go-utils@v1.6.0 +``` + +**Note:** The release documentation can be found in GitLab, by navigating to the new tag. For example: https://gitlab.com/uafrica/go-utils/-/tags/v1.6.0 diff --git a/s3/s3.go b/s3/s3.go index f2639419b18db2e7449335b82db8f724fabac981..ddae2b0fac635365527605b0fdf64103cf0e31df 100644 --- a/s3/s3.go +++ b/s3/s3.go @@ -27,6 +27,12 @@ type S3UploadResponse struct { FileSize int `json:"file_size"` } +type S3UploadSettings struct { + MimeType MIMEType + RetrieveSignedUrl bool + ExpiryDuration *time.Duration +} + type MIMEType string const ( @@ -48,6 +54,9 @@ const ( // MIMETypeImage defines the constant for the Image MIME type. MIMETypeImage MIMEType = "image/*" + // MIMETypePNG defines the constant for the PNG MIME type. + MIMETypePNG MIMEType = "image/png" + // MIMETypeDefault defines the constant for the default MIME type. MIMETypeDefault MIMEType = "application/octet-stream" @@ -68,7 +77,7 @@ func NewSession(session *session.Session) *SessionWithHelpers { } } -func (s SessionWithHelpers) Upload(data []byte, bucket, fileName string, metadata *map[string]*string, isDebug bool) (*s3.PutObjectOutput, error) { +func (s SessionWithHelpers) Upload(data []byte, bucket, fileName string, metadata *map[string]*string) (*s3.PutObjectOutput, error) { mimeType := getTypeForFilename(fileName) putInput := &s3.PutObjectInput{ Bucket: aws.String(bucket), @@ -89,18 +98,21 @@ func (s SessionWithHelpers) Upload(data []byte, bucket, fileName string, metadat return response, nil } -func (s SessionWithHelpers) UploadWith1DayExpiry(data []byte, bucket, fileName string, mimeType MIMEType, isDebug bool) (string, error) { - if mimeType == "" { - mimeType = getTypeForFilename(fileName) +func (s SessionWithHelpers) UploadWithSettings(data []byte, bucket, fileName string, settings S3UploadSettings) (string, error) { + if settings.MimeType == "" { + settings.MimeType = getTypeForFilename(fileName) } - expiry := time.Now().Add(24 * time.Hour) putInput := &s3.PutObjectInput{ Bucket: aws.String(bucket), Key: aws.String(fileName), - ContentType: aws.String(string(mimeType)), + ContentType: aws.String(string(settings.MimeType)), Body: bytes.NewReader(data), - Expires: &expiry, + } + + if settings.ExpiryDuration != nil { + expiry := time.Now().Add(*settings.ExpiryDuration) + putInput.Expires = &expiry } _, err := s.S3Session.PutObject(putInput) @@ -108,33 +120,33 @@ func (s SessionWithHelpers) UploadWith1DayExpiry(data []byte, bucket, fileName s return "", err } - return s.GetSignedDownloadURL(bucket, fileName, 24*time.Hour, isDebug) + if settings.RetrieveSignedUrl { + return s.GetSignedDownloadURL(bucket, fileName, 24*time.Hour) + } + + return "", nil } -func (s SessionWithHelpers) UploadWithExpiry(data []byte, bucket, fileName string, expiryDuration time.Duration, mimeType MIMEType, isDebug bool) (string, error) { +func (s SessionWithHelpers) UploadWith1DayExpiry(data []byte, bucket, fileName string, mimeType MIMEType) (string, error) { if mimeType == "" { mimeType = getTypeForFilename(fileName) } - expiry := time.Now().Add(expiryDuration) - putInput := &s3.PutObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(fileName), - ContentType: aws.String(string(mimeType)), - Body: bytes.NewReader(data), - Expires: &expiry, - } - - _, err := s.S3Session.PutObject(putInput) + expiry := 24 * time.Hour + signedUrl, err := s.UploadWithSettings(data, bucket, fileName, S3UploadSettings{ + MimeType: mimeType, + RetrieveSignedUrl: true, + ExpiryDuration: &expiry, + }) if err != nil { return "", err } - return s.GetSignedDownloadURL(bucket, fileName, 24*time.Hour, isDebug) + return signedUrl, nil } // GetSignedDownloadURL gets a signed download URL for the duration. If scv is nil, a new session will be created. -func (s SessionWithHelpers) GetSignedDownloadURL(bucket string, fileName string, duration time.Duration, isDebug bool) (string, error) { +func (s SessionWithHelpers) GetSignedDownloadURL(bucket string, fileName string, duration time.Duration) (string, error) { getInput := &s3.GetObjectInput{ Bucket: aws.String(bucket), Key: aws.String(fileName), @@ -173,10 +185,15 @@ func (s SessionWithHelpers) FileExists(bucket string, fileName string) (bool, er } // UploadWithFileExtension will upload a file to S3 and return a standard S3UploadResponse. -func (s SessionWithHelpers) UploadWithFileExtension(data []byte, bucket, filePrefix string, fileExt string, mimeType MIMEType, isDebug bool) (*S3UploadResponse, error) { +func (s SessionWithHelpers) UploadWithFileExtension(data []byte, bucket, filePrefix, fileExt string, mimeType MIMEType) (*S3UploadResponse, error) { fileName := fmt.Sprintf("%s_%s.%s", filePrefix, uuid.New().String(), fileExt) - uploadUrl, err := s.UploadWith1DayExpiry(data, bucket, fileName, mimeType, isDebug) + duration := 24 * time.Hour + uploadUrl, err := s.UploadWithSettings(data, bucket, fileName, S3UploadSettings{ + MimeType: mimeType, + RetrieveSignedUrl: true, + ExpiryDuration: &duration, + }) if err != nil { return nil, err } @@ -255,9 +272,9 @@ func (s SessionWithHelpers) CopyObjectBucketToBucket(sourceBucket string, destin // copy the file copySource := url.QueryEscape(sourceBucket + "/" + sourceFileName) copyObjectInput := &s3.CopyObjectInput{ - Bucket: aws.String(destinationBucket), //destination bucket - CopySource: aws.String(copySource), //source path (ie: myBucket/myFile.csv) - Key: aws.String(destinationFilename), //filename on destination + Bucket: aws.String(destinationBucket), // destination bucket + CopySource: aws.String(copySource), // source path (ie: myBucket/myFile.csv) + Key: aws.String(destinationFilename), // filename on destination } _, err := s.S3Session.CopyObject(copyObjectInput) if err != nil { @@ -298,12 +315,19 @@ func (s SessionWithHelpers) DeleteObjectFromBucket(bucket string, fileName strin } func GetS3FileKey(fileName string, folder string) string { + var fileKey string + // Trim leading and trailing slashes fileName = strings.TrimLeft(fileName, "/") fileName = strings.TrimRight(fileName, "/") - folder = strings.TrimLeft(folder, "/") - folder = strings.TrimRight(folder, "/") + if folder != "" { + folder = strings.TrimLeft(folder, "/") + folder = strings.TrimRight(folder, "/") + fileKey += "/" + folder + } + + fileKey += "/" + fileName - return "/" + folder + "/" + fileName + return fileKey } diff --git a/string_utils/snake.go b/string_utils/snake.go deleted file mode 100644 index 358d7fa5e2e503e1f417fc507bd659cbbb30f8f2..0000000000000000000000000000000000000000 --- a/string_utils/snake.go +++ /dev/null @@ -1,11 +0,0 @@ -package string_utils - -import "regexp" - -const snakeCasePattern = `[a-z]([a-z0-9_]*[a-z0-9])*` - -var snakeCaseRegex = regexp.MustCompile("^" + snakeCasePattern + "$") - -func IsSnakeCase(name string) bool { - return snakeCaseRegex.MatchString(name) -} diff --git a/string_utils/string_utils.go b/string_utils/string_utils.go index fc871f45f011f9f7e0f216570d23a6342dcffbde..bd219a790924e89867fc46680afb513e44cf9b49 100644 --- a/string_utils/string_utils.go +++ b/string_utils/string_utils.go @@ -14,6 +14,23 @@ import ( "golang.org/x/text/unicode/norm" ) +const snakeCasePattern = `[a-z]([a-z0-9_]*[a-z0-9])*` + +var snakeCaseRegex = regexp.MustCompile("^" + snakeCasePattern + "$") + +func IsSnakeCase(name string) bool { + return snakeCaseRegex.MatchString(name) +} + +func SnakeToKebabString(s string) string { + s = strings.TrimSpace(s) + + re := regexp.MustCompile("(_)") + s = re.ReplaceAllString(s, "-") + + return s +} + // ReplaceNonSpacingMarks removes diacritics e.g. êžů becomes ezu func ReplaceNonSpacingMarks(str string) string { t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) // Mn: non-spacing marks