-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
329 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package auth | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
// User represents a user object. | ||
type User struct { | ||
Id int `json:"id"` | ||
Username string `json:"username"` | ||
Branch Branch `json:"branch"` | ||
Roles []string `json:"roles"` | ||
} | ||
|
||
// Branch represents a branch object. | ||
type Branch struct { | ||
Id int `json:"id"` | ||
} | ||
|
||
// Authenticate authenticates a user based on the Authorization header. | ||
// It makes a request to the auth service to validate the token and retrieve user information. | ||
func Authenticate(c *gin.Context) bool { | ||
viper.SetDefault("AUTH_API_ME", "/") | ||
|
||
authHeader := c.GetHeader("Authorization") | ||
|
||
if authHeader == "" || len(authHeader) < 7 || authHeader[0:7] != "Bearer " { | ||
return false | ||
} | ||
|
||
token := authHeader[7:] | ||
|
||
req, err := http.NewRequest("GET", viper.GetString("AUTH_API_ME"), nil) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
req.Header.Set("Authorization", "Bearer "+token) | ||
|
||
client := &http.Client{} | ||
resp, err := client.Do(req) | ||
if err != nil { | ||
return false | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
return false | ||
} | ||
|
||
var user User | ||
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { | ||
return false | ||
} | ||
|
||
c.Set("user", user) | ||
|
||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package auth | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/spf13/viper" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestAuthenticate(t *testing.T) { | ||
mockAuthService := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if r.Header.Get("Authorization") != "Bearer test-token" { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
|
||
user := User{ | ||
Id: 1, | ||
Username: "testuser", | ||
Branch: Branch{ | ||
Id: 1, | ||
}, | ||
Roles: []string{"admin"}, | ||
} | ||
json.NewEncoder(w).Encode(user) | ||
})) | ||
defer mockAuthService.Close() | ||
|
||
viper.Set("AUTH_API_ME", mockAuthService.URL) | ||
|
||
c, _ := gin.CreateTestContext(httptest.NewRecorder()) | ||
c.Request, _ = http.NewRequest("GET", "/", nil) | ||
|
||
testCases := []struct { | ||
name string | ||
authorization string | ||
expected bool | ||
}{ | ||
{"Missing Authorization header", "", false}, | ||
{"Invalid Authorization header", "InvalidToken", false}, | ||
{"Valid Authorization header", "Bearer test-token", true}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
c.Request.Header.Set("Authorization", tc.authorization) | ||
assert.Equal(t, tc.expected, Authenticate(c)) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package cover | ||
|
||
import ( | ||
"fmt" | ||
"image" | ||
"image/jpeg" | ||
"image/png" | ||
"mime/multipart" | ||
"net/http" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/gin-gonic/gin" | ||
|
||
"github.com/nfnt/resize" | ||
) | ||
|
||
const uploadsDir = "uploads" | ||
|
||
// SaveCover saves the uploaded cover image in different sizes. | ||
func SaveCover(c *gin.Context, imageUUID string) { | ||
imageData, err := c.FormFile("cover") | ||
if err != nil { | ||
c.JSON(http.StatusBadRequest, gin.H{"error": "Image upload required"}) | ||
return | ||
} | ||
|
||
if err := saveResizedImages(c, imageData, imageUUID); err != nil { | ||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"}) | ||
return | ||
} | ||
|
||
c.Status(http.StatusOK) | ||
} | ||
|
||
func saveResizedImages(c *gin.Context, imageData *multipart.FileHeader, imageUUID string) error { | ||
imagePath, err := saveUploadedImage(c, imageData, imageUUID) | ||
if err != nil { | ||
return fmt.Errorf("failed to save uploaded image: %w", err) | ||
} | ||
defer os.Remove(imagePath) | ||
|
||
sizes := []struct { | ||
width uint | ||
suffix string | ||
}{ | ||
{400, "l"}, | ||
{200, "m"}, | ||
{100, "s"}, | ||
} | ||
|
||
for _, size := range sizes { | ||
resizedImagePath := filepath.Join(uploadsDir, fmt.Sprintf("%s-%s%s", imageUUID, size.suffix, filepath.Ext(imageData.Filename))) | ||
|
||
if err := resizeAndSaveImage(imagePath, resizedImagePath, size.width); err != nil { | ||
return fmt.Errorf("failed to resize image: %w", err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func saveUploadedImage(c *gin.Context, imageData *multipart.FileHeader, imageUUID string) (string, error) { | ||
imageFilename := fmt.Sprintf("%s%s", imageUUID, filepath.Ext(imageData.Filename)) | ||
currentDir, _ := os.Getwd() | ||
uploadsDirPath := filepath.Join(currentDir, uploadsDir) | ||
|
||
if err := os.MkdirAll(uploadsDirPath, 0755); err != nil { | ||
return "", fmt.Errorf("failed to create uploads directory") | ||
} | ||
|
||
imagePath := filepath.Join(uploadsDirPath, imageFilename) | ||
if err := c.SaveUploadedFile(imageData, imagePath); err != nil { | ||
return "", fmt.Errorf("failed to save image") | ||
} | ||
|
||
return imagePath, nil | ||
} | ||
|
||
func resizeAndSaveImage(imagePath string, resizedImagePath string, width uint) error { | ||
file, err := os.Open(imagePath) | ||
if err != nil { | ||
return fmt.Errorf("failed to open image") | ||
} | ||
defer file.Close() | ||
|
||
img, err := decodeImage(file, imagePath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
resizedImage := resize.Resize(width, 0, img, resize.Lanczos3) | ||
|
||
out, err := os.Create(resizedImagePath) | ||
if err != nil { | ||
return fmt.Errorf("failed to create resized image file") | ||
} | ||
defer out.Close() | ||
|
||
if err := encodeImage(out, resizedImage, imagePath); err != nil { | ||
return fmt.Errorf("failed to encode resized image") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func decodeImage(file *os.File, imagePath string) (image.Image, error) { | ||
ext := filepath.Ext(imagePath) | ||
|
||
switch ext { | ||
case ".jpg", ".jpeg": | ||
return jpeg.Decode(file) | ||
case ".png": | ||
return png.Decode(file) | ||
default: | ||
return nil, fmt.Errorf("unsupported image format: %s", ext) | ||
} | ||
} | ||
|
||
func encodeImage(out *os.File, resizedImage image.Image, imagePath string) error { | ||
ext := filepath.Ext(imagePath) | ||
|
||
switch ext { | ||
case ".jpg", ".jpeg": | ||
return jpeg.Encode(out, resizedImage, nil) | ||
case ".png": | ||
return png.Encode(out, resizedImage) | ||
default: | ||
return fmt.Errorf("unsupported image format: %s", ext) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package cover | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
"mime/multipart" | ||
"net/http" | ||
"net/http/httptest" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestSaveCover(t *testing.T) { | ||
gin.SetMode(gin.TestMode) | ||
|
||
router := gin.Default() | ||
router.POST("/upload", func(c *gin.Context) { | ||
SaveCover(c, "36ee6d5c-820b-4f0c-9637-73b63dacc2a7") | ||
}) | ||
|
||
imageData, err := os.ReadFile("test.jpg") | ||
if err != nil { | ||
t.Fatalf("Error reading image file: %v", err) | ||
} | ||
|
||
body := new(bytes.Buffer) | ||
writer := multipart.NewWriter(body) | ||
part, _ := writer.CreateFormFile("cover", "test.jpg") | ||
_, _ = io.Copy(part, bytes.NewReader(imageData)) | ||
writer.Close() | ||
|
||
req, _ := http.NewRequest("POST", "/upload", body) | ||
req.Header.Set("Content-Type", writer.FormDataContentType()) | ||
|
||
w := httptest.NewRecorder() | ||
router.ServeHTTP(w, req) | ||
|
||
assert.Equal(t, http.StatusOK, w.Code) | ||
|
||
currentDir, _ := os.Getwd() | ||
expectedFilePaths := []string{ | ||
filepath.Join(currentDir, uploadsDir, "36ee6d5c-820b-4f0c-9637-73b63dacc2a7-l.jpg"), | ||
filepath.Join(currentDir, uploadsDir, "36ee6d5c-820b-4f0c-9637-73b63dacc2a7-m.jpg"), | ||
filepath.Join(currentDir, uploadsDir, "36ee6d5c-820b-4f0c-9637-73b63dacc2a7-s.jpg"), | ||
} | ||
|
||
for _, expectedFilePath := range expectedFilePaths { | ||
if _, err := os.Stat(expectedFilePath); os.IsNotExist(err) { | ||
t.Errorf("Expected file %s to exist", expectedFilePath) | ||
} else { | ||
os.Remove(expectedFilePath) | ||
} | ||
} | ||
|
||
assert.NoFileExists(t, filepath.Join(currentDir, uploadsDir, "36ee6d5c-820b-4f0c-9637-73b63dacc2a7.jpg")) | ||
|
||
os.RemoveAll(filepath.Join(currentDir, uploadsDir)) | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.