Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
67be9e7
wip
Mujhtech Dec 25, 2024
25437fc
add configuration validation and unit tests for LoadConfig
Mujhtech Dec 26, 2024
5a55bab
draft
Mujhtech Dec 26, 2024
e7110bf
Merge branch 'main' into tusd-implementation
Mujhtech Dec 26, 2024
ca72d90
feat(file-upload): add folder_id to file creation schema and implemen…
Mujhtech Dec 28, 2024
3ccf7a5
feat(cors): implement CORS middleware with configurable options
Mujhtech Dec 28, 2024
7f45b0d
feat(file-upload): rename createFile to uploadFile and update request…
Mujhtech Dec 28, 2024
78fdb15
feat(config): add CORS configuration to the application settings
Mujhtech Dec 28, 2024
0601f25
feat(cache): implement Redis caching with configurable provider
Mujhtech Dec 28, 2024
60602ad
feat(layout): update grid layout for folders and files sections
Mujhtech Dec 28, 2024
21352a0
feat(file): add status field and new file statuses to the file model
Mujhtech Dec 29, 2024
0c8c5f3
feat(dependencies): add RabbitMQ AMQP client library and update go-ub…
Mujhtech Dec 29, 2024
7de5dd7
feat(services): implement CreateFileService for file creation and man…
Mujhtech Dec 29, 2024
081cb0a
feat(config): update AWS region and change pubsub provider to in-memory
Mujhtech Dec 29, 2024
d8e482f
feat(api): enhance file handling with upload and delete endpoints, an…
Mujhtech Dec 29, 2024
a9986ef
feat(auth): enhance redirect functionality to support URL validation …
Mujhtech Dec 29, 2024
8cf8eb0
feat(config): add Redis as cache provider in configuration
Mujhtech Dec 29, 2024
3f7e9c6
fix(protocol): update configuration references and improve logging in…
Mujhtech Dec 29, 2024
3abc245
feat(pubsub): add namespace options for publish and subscribe, enhanc…
Mujhtech Dec 29, 2024
cd18a7f
feat(upload): implement file upload functionality with authentication…
Mujhtech Dec 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions backend/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import (
chiMiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/mujhtech/s3ase/api/handler"
"github.com/mujhtech/s3ase/api/middleware"
"github.com/mujhtech/s3ase/cache"
"github.com/mujhtech/s3ase/config"
"github.com/mujhtech/s3ase/database/store"
"github.com/mujhtech/s3ase/internal/pkg/s3store"
"github.com/mujhtech/s3ase/internal/pkg/sse"
"github.com/mujhtech/s3ase/internal/protocol"
"github.com/mujhtech/s3ase/job"
"github.com/rs/zerolog/hlog"
)
Expand All @@ -23,18 +25,21 @@ type Api struct {
cfg *config.Config
store *store.Store
job *job.Job
cache cache.Cache
}

func New(
cfg *config.Config,
ctx context.Context,
cache cache.Cache,
job *job.Job,
store *store.Store,
s3 *s3store.S3Store,
sse sse.Streamer,
protocol *protocol.Protocol,
) (*Api, error) {

h, err := handler.New(cfg, ctx, job, store, s3, sse)
h, err := handler.New(cfg, ctx, cache, job, store, s3, sse, protocol)
if err != nil {
return nil, fmt.Errorf("failed to create handler: %w", err)
}
Expand All @@ -44,6 +49,7 @@ func New(
cfg: cfg,
store: store,
job: job,
cache: cache,
}, nil

}
Expand All @@ -58,6 +64,7 @@ func (a *Api) BuildRouter() *chi.Mux {
router.Use(hlog.MethodHandler("http.method"))
router.Use(middleware.WriteRequestIDHeader())
router.Use(middleware.HLogAccessLogHandler())
router.Use(middleware.ApplyCORS(a.cfg))

router.Route("/api", func(r chi.Router) {

Expand All @@ -68,7 +75,7 @@ func (a *Api) BuildRouter() *chi.Mux {
r.Route("/ui", func(r chi.Router) {

r.Use(
chiMiddleware.Maybe(middleware.RequiredUserAuth(a.cfg, a.store), shouldAllowAuth),
chiMiddleware.Maybe(middleware.RequiredUserAuth(a.cfg, a.store, a.cache), shouldAllowAuth),
middleware.AppIdRequestHeader(a.store),
chiMiddleware.Maybe(middleware.RequiredAppMember(a.cfg, a.store), shouldAllowMember),
)
Expand Down Expand Up @@ -122,6 +129,9 @@ func (a *Api) BuildRouter() *chi.Mux {
r.Route("/files", func(r chi.Router) {
r.Get("/", a.handler.GetFiles)
r.Get(fmt.Sprintf("/{%s}", handler.FileParamID), a.handler.GetFile)
r.Post("/", a.handler.UploadFile)
r.Patch(fmt.Sprintf("/{%s}", handler.FileParamID), a.handler.UploadFile)
r.Delete(fmt.Sprintf("/{%s}", handler.FileParamID), a.handler.DeleteFile)
})

// domain
Expand Down
6 changes: 6 additions & 0 deletions backend/api/dto/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ type FileQueryDto struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
}

type CreateFileRequestDto struct {
Name string `json:"name"`
FolderID string `json:"folder_id,omitempty"`
Description string `json:"description,omitempty"`
}
12 changes: 6 additions & 6 deletions backend/api/handler/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (h *Handler) Authenticate(w http.ResponseWriter, r *http.Request) {
query := url.Values{}
query.Add("error", err.Error())

if err = response.Redirect(w, r, fmt.Sprintf("%s?%s", redirectUrl, query.Encode()), http.StatusTemporaryRedirect); err != nil {
if err = response.Redirect(w, r, fmt.Sprintf("%s?%s", redirectUrl, query.Encode()), http.StatusTemporaryRedirect, true); err != nil {
log.Error().Err(err).Msg("failed to redirect")
return
}
Expand All @@ -68,7 +68,7 @@ func (h *Handler) Authenticate(w http.ResponseWriter, r *http.Request) {
HttpOnly: true,
})

if err = response.Redirect(w, r, authUrl, http.StatusTemporaryRedirect); err != nil {
if err = response.Redirect(w, r, authUrl, http.StatusTemporaryRedirect, true); err != nil {
log.Error().Err(err).Msg("failed to redirect")
return
}
Expand Down Expand Up @@ -141,7 +141,7 @@ func (h *Handler) AuthenticateCallback(w http.ResponseWriter, r *http.Request) {
}

// create token
tokenManager := jwtAuth.NewJWTAuth(h.cfg, h.store.UserRepo, h.store.TokenRepo)
tokenManager := jwtAuth.NewJWTAuth(h.cfg, h.store.UserRepo, h.store.TokenRepo, h.cache)

_, authToken, err := tokenManager.CreateToken(ctx, user.ID, "bearer")

Expand Down Expand Up @@ -172,7 +172,7 @@ func (h *Handler) AuthenticateCallback(w http.ResponseWriter, r *http.Request) {
HttpOnly: true,
})

if err = response.Redirect(w, r, redirectTo.String(), http.StatusTemporaryRedirect); err != nil {
if err = response.Redirect(w, r, redirectTo.String(), http.StatusTemporaryRedirect, true); err != nil {
log.Error().Err(err).Msg("failed to redirect")
return
}
Expand All @@ -199,7 +199,7 @@ func (h *Handler) AuthenticateCallbackPost(w http.ResponseWriter, r *http.Reques

query.Add("error", err.Error())

if err = response.Redirect(w, r, fmt.Sprintf("%s?%s", redirectUrl, query.Encode()), http.StatusTemporaryRedirect); err != nil {
if err = response.Redirect(w, r, fmt.Sprintf("%s?%s", redirectUrl, query.Encode()), http.StatusTemporaryRedirect, true); err != nil {
log.Error().Err(err).Msg("failed to redirect")
return
}
Expand All @@ -210,7 +210,7 @@ func (h *Handler) AuthenticateCallbackPost(w http.ResponseWriter, r *http.Reques
query.Add("state", authState)
query.Add("code", code)

if err = response.Redirect(w, r, fmt.Sprintf("/%s/callback?%s", provider.Name(), query.Encode()), http.StatusSeeOther); err != nil {
if err = response.Redirect(w, r, fmt.Sprintf("/%s/callback?%s", provider.Name(), query.Encode()), http.StatusSeeOther, true); err != nil {
log.Error().Err(err).Msg("failed to redirect")
return
}
Expand Down
88 changes: 88 additions & 0 deletions backend/api/handler/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ func getFilesQueryParams(r *http.Request) *dto.FileQueryDto {
}
}

func getCreateFileQuery(r *http.Request) (*dto.CreateFileRequestDto, error) {
folderId, _ := queryParam(r, "folder_id")
name, err := queryParamOrError(r, "name")

if err != nil {
return nil, err
}

return &dto.CreateFileRequestDto{
FolderID: folderId,
Name: name,
}, nil
}

func (h *Handler) GetFiles(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

Expand Down Expand Up @@ -101,3 +115,77 @@ func (h *Handler) GetFile(w http.ResponseWriter, r *http.Request) {

_ = response.Ok(w, r, "file retrieved", file)
}

func (h *Handler) UploadFile(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

session, app, err := middleware.AuthSessionAndAppFrom(ctx)

if err != nil {
_ = response.Unauthorized(w, r, err)
return
}

dst, err := getCreateFileQuery(r)

if err != nil {
_ = response.BadRequest(w, r, err)
return
}

createFileService := services.CreateFileService{
App: app,
FolderRepo: h.store.FolderRepo,
FileRepo: h.store.FileRepo,
User: session.User,
Body: dst,
}

h.protocol.UploadFile(createFileService, w, r)

// id := uuid.New().String()
// name := "test.txt"
// progress := 0

// // Create background context for the goroutine
// bgCtx := context.Background()

// if err = h.sse.Publish(ctx, app.ID, sse.EventTypeUploadStarted, UploadProgress{
// FileID: id,
// Name: name,
// Status: UploadProgressStatusStarted,
// Progress: progress,
// }); err != nil {
// log.Printf("failed to publish upload started event: %v", err)
// }

// Use background context in goroutine
// go func(ctx context.Context, appID string) {
// for progress < 100 {
// progress += 5
// if err = h.sse.Publish(ctx, appID, sse.EventTypeUploadProgress, UploadProgress{
// FileID: id,
// Name: name,
// Status: UploadProgressStatusUploading,
// Progress: progress,
// }); err != nil {
// log.Printf("failed to publish upload progress event: %v", err)
// }
// time.Sleep(1 * time.Second)
// if progress == 100 {
// if err = h.sse.Publish(ctx, appID, sse.EventTypeUploadCompleted, UploadProgress{
// FileID: id,
// Name: name,
// Status: UploadProgressStatusCompleted,
// Progress: progress,
// }); err != nil {
// log.Printf("failed to publish upload completed event: %v", err)
// }
// }
// }
// }(bgCtx, app.ID)

//_ = response.Ok(w, r, "file uploaded", nil)
}

func (h *Handler) DeleteFile(w http.ResponseWriter, r *http.Request) {}
32 changes: 20 additions & 12 deletions backend/api/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,45 @@ package handler
import (
"context"

"github.com/mujhtech/s3ase/cache"
"github.com/mujhtech/s3ase/config"
"github.com/mujhtech/s3ase/database/store"
"github.com/mujhtech/s3ase/internal/pkg/s3store"
"github.com/mujhtech/s3ase/internal/pkg/sse"
"github.com/mujhtech/s3ase/internal/protocol"
"github.com/mujhtech/s3ase/job"
)

type Handler struct {
cfg *config.Config
ctx context.Context
store *store.Store
job *job.Job
s3 *s3store.S3Store
sse sse.Streamer
cfg *config.Config
ctx context.Context
cache cache.Cache
store *store.Store
job *job.Job
s3 *s3store.S3Store
sse sse.Streamer
protocol *protocol.Protocol
}

func New(
cfg *config.Config,
ctx context.Context,
cache cache.Cache,
job *job.Job,
store *store.Store,
s3 *s3store.S3Store,
sse sse.Streamer,
protocol *protocol.Protocol,
) (*Handler, error) {

return &Handler{
cfg: cfg,
ctx: ctx,
store: store,
job: job,
s3: s3,
sse: sse,
cfg: cfg,
ctx: ctx,
cache: cache,
store: store,
job: job,
s3: s3,
sse: sse,
protocol: protocol,
}, nil
}
5 changes: 3 additions & 2 deletions backend/api/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"

"github.com/mujhtech/s3ase/auth"
"github.com/mujhtech/s3ase/cache"
"github.com/mujhtech/s3ase/config"
"github.com/mujhtech/s3ase/database/store"
"github.com/mujhtech/s3ase/internal/pkg/response"
Expand All @@ -14,12 +15,12 @@ const (
authSessionKey key = iota
)

func RequiredUserAuth(cfg *config.Config, store *store.Store) func(http.Handler) http.Handler {
func RequiredUserAuth(cfg *config.Config, store *store.Store, cache cache.Cache) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

jwt := auth.NewJWTAuth(cfg, store.UserRepo, store.TokenRepo)
jwt := auth.NewJWTAuth(cfg, store.UserRepo, store.TokenRepo, cache)

session, err := jwt.UserAuthenticate(r)

Expand Down
21 changes: 21 additions & 0 deletions backend/api/middleware/cors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package middleware

import (
"net/http"

"github.com/go-chi/cors"
"github.com/mujhtech/s3ase/config"
)

func ApplyCORS(config *config.Config) func(http.Handler) http.Handler {
return cors.New(
cors.Options{
AllowedOrigins: config.Cors.AllowedOrigins,
AllowedMethods: config.Cors.AllowedMethods,
AllowedHeaders: config.Cors.AllowedHeaders,
ExposedHeaders: config.Cors.ExposedHeaders,
AllowCredentials: config.Cors.AllowCredentials,
MaxAge: config.Cors.MaxAge,
},
).Handler
}
10 changes: 9 additions & 1 deletion backend/auth/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
gojwt "github.com/golang-jwt/jwt"
"github.com/google/uuid"
"github.com/gotidy/ptr"
"github.com/mujhtech/s3ase/cache"
"github.com/mujhtech/s3ase/config"
"github.com/mujhtech/s3ase/database/models"
"github.com/mujhtech/s3ase/database/store"
Expand All @@ -27,6 +28,7 @@ type JWTAuth struct {
cfg *config.Config
userRepo store.UserRepository
tokenRepo store.TokenRepository
cache cache.Cache
}

type Claims struct {
Expand All @@ -42,11 +44,12 @@ type SubClaimsToken struct {
ID string `json:"id,omitempty"`
}

func NewJWTAuth(cfg *config.Config, userRepo store.UserRepository, tokenRepo store.TokenRepository) Auth {
func NewJWTAuth(cfg *config.Config, userRepo store.UserRepository, tokenRepo store.TokenRepository, cache cache.Cache) Auth {
return &JWTAuth{
cfg: cfg,
userRepo: userRepo,
tokenRepo: tokenRepo,
cache: cache,
}
}

Expand Down Expand Up @@ -138,6 +141,11 @@ func (j JWTAuth) CreateToken(
return nil, "", fmt.Errorf("failed to create jwt token: %w", err)
}

// set token in cache.
if err := j.cache.Set(ctx, token.Value, token.ID, *lifetime); err != nil {
return nil, "", fmt.Errorf("failed to set token in cache: %w", err)
}

return &token, jwtToken, nil
}

Expand Down
Loading
Loading