migrate to WebSocket for frontend and backend communication
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,5 +6,7 @@
|
|||||||
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
docker-compose.staging.yaml
|
||||||
|
|
||||||
*_templ.txt
|
*_templ.txt
|
||||||
*_templ.go
|
*_templ.go
|
@ -9,7 +9,7 @@ RUN npm install -g tailwindcss
|
|||||||
RUN npm install -g javascript-obfuscator
|
RUN npm install -g javascript-obfuscator
|
||||||
RUN npm install -g clean-css-cli
|
RUN npm install -g clean-css-cli
|
||||||
RUN npx tailwindcss -i ./public/input.css -o ./tmp/output.css
|
RUN npx tailwindcss -i ./public/input.css -o ./tmp/output.css
|
||||||
RUN javascript-obfuscator ./public/upload.js --compact true --self-defending true --output ./public/upload_obfuscated.js
|
RUN javascript-obfuscator ./public/main.js --compact true --self-defending true --output ./public/main_obfuscated.js
|
||||||
RUN javascript-obfuscator ./public/validatePassword.js --compact true --self-defending true --output ./public/validatePassword_obfuscated.js
|
RUN javascript-obfuscator ./public/validatePassword.js --compact true --self-defending true --output ./public/validatePassword_obfuscated.js
|
||||||
RUN javascript-obfuscator ./public/websocket.js --compact true --self-defending true --output ./public/websocket_obfuscated.js
|
RUN javascript-obfuscator ./public/websocket.js --compact true --self-defending true --output ./public/websocket_obfuscated.js
|
||||||
RUN cleancss -o ./public/output.css ./tmp/output.css
|
RUN cleancss -o ./public/output.css ./tmp/output.css
|
||||||
@ -19,7 +19,7 @@ FROM golang:1.22.2-alpine3.19 AS go_builder
|
|||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=node_builder /src/public /src/public
|
COPY --from=node_builder /src/public /src/public
|
||||||
COPY --from=node_builder /src/public/upload_obfuscated.js /src/public/upload.js
|
COPY --from=node_builder /src/public/main_obfuscated.js /src/public/main.js
|
||||||
COPY --from=node_builder /src/public/validatePassword_obfuscated.js /src/public/validatePassword.js
|
COPY --from=node_builder /src/public/validatePassword_obfuscated.js /src/public/validatePassword.js
|
||||||
COPY --from=node_builder /src/public/websocket_obfuscated.js /src/public/websocket.js
|
COPY --from=node_builder /src/public/websocket_obfuscated.js /src/public/websocket.js
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ RUN update-ca-certificates
|
|||||||
RUN go install github.com/a-h/templ/cmd/templ@$(go list -m -f '{{ .Version }}' github.com/a-h/templ)
|
RUN go install github.com/a-h/templ/cmd/templ@$(go list -m -f '{{ .Version }}' github.com/a-h/templ)
|
||||||
RUN templ generate
|
RUN templ generate
|
||||||
RUN go build -o ./tmp/main
|
RUN go build -o ./tmp/main
|
||||||
RUN rm /src/public/validatePassword_obfuscated.js /src/public/upload_obfuscated.js
|
RUN rm /src/public/validatePassword_obfuscated.js /src/public/main_obfuscated.js /src/public/websocket_obfuscated.js
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
|
||||||
|
2
go.mod
2
go.mod
@ -7,6 +7,7 @@ require (
|
|||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/redis/go-redis/v9 v9.6.1
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/xlzd/gotp v0.1.0
|
github.com/xlzd/gotp v0.1.0
|
||||||
@ -31,7 +32,6 @@ require (
|
|||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/redis/go-redis/v9 v9.6.1 // indirect
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -2,6 +2,10 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
|||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
|
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
|
||||||
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
|
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -1,144 +0,0 @@
|
|||||||
package initialisation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fossyy/filekeeper/app"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/fossyy/filekeeper/types"
|
|
||||||
"github.com/fossyy/filekeeper/types/models"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func POST(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userSession := r.Context().Value("user").(types.User)
|
|
||||||
|
|
||||||
body, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileInfo types.FileInfo
|
|
||||||
if err := json.Unmarshal(body, &fileInfo); err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fileData, err := app.Server.Service.GetUserFile(fileInfo.Name, userSession.UserID.String())
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
upload, err := handleNewUpload(userSession, fileInfo)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fileData = &types.FileWithDetail{
|
|
||||||
ID: fileData.ID,
|
|
||||||
OwnerID: fileData.OwnerID,
|
|
||||||
Name: fileData.Name,
|
|
||||||
Size: fileData.Size,
|
|
||||||
Downloaded: fileData.Downloaded,
|
|
||||||
}
|
|
||||||
fileData.Chunk = make(map[string]bool)
|
|
||||||
fileData.Done = true
|
|
||||||
saveFolder := filepath.Join("uploads", userSession.UserID.String(), fileData.ID.String(), fileData.Name)
|
|
||||||
for i := 0; i <= int(fileInfo.Chunk-1); i++ {
|
|
||||||
fileName := fmt.Sprintf("%s/chunk_%d", saveFolder, i)
|
|
||||||
|
|
||||||
if _, err := os.Stat(fileName); os.IsNotExist(err) {
|
|
||||||
fileData.Chunk[fmt.Sprintf("chunk_%d", i)] = false
|
|
||||||
fileData.Done = false
|
|
||||||
} else {
|
|
||||||
fileData.Chunk[fmt.Sprintf("chunk_%d", i)] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
respondJSON(w, upload)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
respondErrorJSON(w, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fileData.Chunk = make(map[string]bool)
|
|
||||||
fileData.Done = true
|
|
||||||
saveFolder := filepath.Join("uploads", userSession.UserID.String(), fileData.ID.String(), fileData.Name)
|
|
||||||
for i := 0; i <= int(fileInfo.Chunk-1); i++ {
|
|
||||||
fileName := fmt.Sprintf("%s/chunk_%d", saveFolder, i)
|
|
||||||
|
|
||||||
if _, err := os.Stat(fileName); os.IsNotExist(err) {
|
|
||||||
fileData.Chunk[fmt.Sprintf("chunk_%d", i)] = false
|
|
||||||
fileData.Done = false
|
|
||||||
} else {
|
|
||||||
fileData.Chunk[fmt.Sprintf("chunk_%d", i)] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
respondJSON(w, fileData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleNewUpload(user types.User, file types.FileInfo) (models.File, error) {
|
|
||||||
uploadDir := "uploads"
|
|
||||||
if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
|
|
||||||
app.Server.Logger.Error(err.Error())
|
|
||||||
err := os.Mkdir(uploadDir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
app.Server.Logger.Error(err.Error())
|
|
||||||
return models.File{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileID := uuid.New()
|
|
||||||
ownerID := user.UserID
|
|
||||||
|
|
||||||
currentDir, _ := os.Getwd()
|
|
||||||
basePath := filepath.Join(currentDir, uploadDir)
|
|
||||||
saveFolder := filepath.Join(basePath, ownerID.String(), fileID.String())
|
|
||||||
if filepath.Dir(saveFolder) != filepath.Join(basePath, ownerID.String()) {
|
|
||||||
return models.File{}, errors.New("invalid path")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := os.MkdirAll(saveFolder, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
app.Server.Logger.Error(err.Error())
|
|
||||||
return models.File{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newFile := models.File{
|
|
||||||
ID: fileID,
|
|
||||||
OwnerID: ownerID,
|
|
||||||
Name: file.Name,
|
|
||||||
Size: file.Size,
|
|
||||||
TotalChunk: file.Chunk - 1,
|
|
||||||
Downloaded: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = app.Server.Database.CreateFile(&newFile)
|
|
||||||
if err != nil {
|
|
||||||
app.Server.Logger.Error(err.Error())
|
|
||||||
return models.File{}, err
|
|
||||||
}
|
|
||||||
return newFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func respondJSON(w http.ResponseWriter, data interface{}) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
if err := json.NewEncoder(w).Encode(data); err != nil {
|
|
||||||
handleError(w, err, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func respondErrorJSON(w http.ResponseWriter, err error, statusCode int) {
|
|
||||||
w.WriteHeader(statusCode)
|
|
||||||
respondJSON(w, map[string]string{"error": err.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleError(w http.ResponseWriter, err error, statusCode int) {
|
|
||||||
http.Error(w, err.Error(), statusCode)
|
|
||||||
app.Server.Logger.Error(err.Error())
|
|
||||||
}
|
|
@ -1,22 +1,63 @@
|
|||||||
package userHandler
|
package userHandler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
"github.com/fossyy/filekeeper/app"
|
"github.com/fossyy/filekeeper/app"
|
||||||
"github.com/fossyy/filekeeper/types"
|
|
||||||
"github.com/fossyy/filekeeper/view/client/user"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/fossyy/filekeeper/session"
|
"github.com/fossyy/filekeeper/session"
|
||||||
|
"github.com/fossyy/filekeeper/types"
|
||||||
|
"github.com/fossyy/filekeeper/types/models"
|
||||||
|
"github.com/fossyy/filekeeper/view/client/user"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errorMessages = map[string]string{
|
var errorMessages = map[string]string{
|
||||||
"password_not_match": "The passwords provided do not match. Please try again.",
|
"password_not_match": "The passwords provided do not match. Please try again.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UploadNewFile ActionType = "UploadNewFile"
|
||||||
|
DeleteFile ActionType = "DeleteFile"
|
||||||
|
Ping ActionType = "Ping"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WebsocketAction struct {
|
||||||
|
Action ActionType `json:"action"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionUploadNewFile struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size uint64 `json:"size"`
|
||||||
|
Chunk uint64 `json:"chunk"`
|
||||||
|
RequestID string `json:"requestID"`
|
||||||
|
}
|
||||||
|
|
||||||
func GET(w http.ResponseWriter, r *http.Request) {
|
func GET(w http.ResponseWriter, r *http.Request) {
|
||||||
var component templ.Component
|
|
||||||
userSession := r.Context().Value("user").(types.User)
|
userSession := r.Context().Value("user").(types.User)
|
||||||
|
if r.Header.Get("upgrade") == "websocket" {
|
||||||
|
upgrade, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handlerWS(upgrade, userSession)
|
||||||
|
}
|
||||||
|
var component templ.Component
|
||||||
sessions, err := session.GetSessions(userSession.Email)
|
sessions, err := session.GetSessions(userSession.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
@ -45,3 +86,159 @@ func GET(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handlerWS(conn *websocket.Conn, userSession types.User) {
|
||||||
|
defer conn.Close()
|
||||||
|
var err error
|
||||||
|
var message []byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, message, err = conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||||
|
fmt.Println("Unexpected connection closure:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Connection closed:", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var action WebsocketAction
|
||||||
|
err = json.Unmarshal(message, &action)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error unmarshalling WebsocketAction:", err)
|
||||||
|
sendErrorResponse(conn, action.Action)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action.Action {
|
||||||
|
case UploadNewFile:
|
||||||
|
var uploadNewFile ActionUploadNewFile
|
||||||
|
err = json.Unmarshal(message, &uploadNewFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error unmarshalling ActionUploadNewFile:", err)
|
||||||
|
sendErrorResponse(conn, action.Action)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var file *models.File
|
||||||
|
file, err = app.Server.Database.GetUserFile(uploadNewFile.Name, userSession.UserID.String())
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
newFile := models.File{
|
||||||
|
ID: uuid.New(),
|
||||||
|
OwnerID: userSession.UserID,
|
||||||
|
Name: uploadNewFile.Name,
|
||||||
|
Size: uploadNewFile.Size,
|
||||||
|
TotalChunk: uploadNewFile.Chunk,
|
||||||
|
Downloaded: 0,
|
||||||
|
}
|
||||||
|
err := app.Server.Database.CreateFile(&newFile)
|
||||||
|
if err != nil {
|
||||||
|
sendErrorResponse(conn, action.Action)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fileData := &types.FileWithDetail{
|
||||||
|
ID: newFile.ID,
|
||||||
|
OwnerID: newFile.OwnerID,
|
||||||
|
Name: newFile.Name,
|
||||||
|
Size: newFile.Size,
|
||||||
|
Downloaded: newFile.Downloaded,
|
||||||
|
}
|
||||||
|
fileData.Chunk = make(map[string]bool)
|
||||||
|
fileData.Done = true
|
||||||
|
saveFolder := filepath.Join("uploads", userSession.UserID.String(), newFile.ID.String(), newFile.Name)
|
||||||
|
for i := 0; i <= int(newFile.TotalChunk); i++ {
|
||||||
|
fileName := fmt.Sprintf("%s/chunk_%d", saveFolder, i)
|
||||||
|
if _, err := os.Stat(fileName); os.IsNotExist(err) {
|
||||||
|
fileData.Chunk[fmt.Sprintf("chunk_%d", i)] = false
|
||||||
|
fileData.Done = false
|
||||||
|
} else {
|
||||||
|
fileData.Chunk[fmt.Sprintf("chunk_%d", i)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendSuccessResponseWithID(conn, action.Action, fileData, uploadNewFile.RequestID)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
sendErrorResponse(conn, action.Action)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileData := &types.FileWithDetail{
|
||||||
|
ID: file.ID,
|
||||||
|
OwnerID: file.OwnerID,
|
||||||
|
Name: file.Name,
|
||||||
|
Size: file.Size,
|
||||||
|
Downloaded: file.Downloaded,
|
||||||
|
Done: false,
|
||||||
|
}
|
||||||
|
fileData.Chunk = make(map[string]bool)
|
||||||
|
fileData.Done = true
|
||||||
|
saveFolder := filepath.Join("uploads", userSession.UserID.String(), fileData.ID.String(), fileData.Name)
|
||||||
|
for i := 0; i <= int(file.TotalChunk-1); i++ {
|
||||||
|
fileName := fmt.Sprintf("%s/chunk_%d", saveFolder, i)
|
||||||
|
|
||||||
|
if _, err := os.Stat(fileName); os.IsNotExist(err) {
|
||||||
|
fileData.Chunk[fmt.Sprintf("chunk_%d", i)] = false
|
||||||
|
fileData.Done = false
|
||||||
|
} else {
|
||||||
|
fileData.Chunk[fmt.Sprintf("chunk_%d", i)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendSuccessResponseWithID(conn, action.Action, fileData, uploadNewFile.RequestID)
|
||||||
|
continue
|
||||||
|
case Ping:
|
||||||
|
sendSuccessResponse(conn, action.Action, map[string]string{"message": "received"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendErrorResponse(conn *websocket.Conn, action ActionType) {
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"action": action,
|
||||||
|
"status": "error",
|
||||||
|
}
|
||||||
|
marshal, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error marshalling error response:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.WriteMessage(websocket.TextMessage, marshal)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error writing error response:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendSuccessResponse(conn *websocket.Conn, action ActionType, response interface{}) {
|
||||||
|
responseJSON := map[string]interface{}{
|
||||||
|
"action": action,
|
||||||
|
"status": "success",
|
||||||
|
"response": response,
|
||||||
|
}
|
||||||
|
marshal, err := json.Marshal(responseJSON)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error marshalling success response:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.WriteMessage(websocket.TextMessage, marshal)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error writing success response:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendSuccessResponseWithID(conn *websocket.Conn, action ActionType, response interface{}, responseID string) {
|
||||||
|
responseJSON := map[string]interface{}{
|
||||||
|
"action": action,
|
||||||
|
"status": "success",
|
||||||
|
"response": response,
|
||||||
|
"responseID": responseID,
|
||||||
|
}
|
||||||
|
marshal, err := json.Marshal(responseJSON)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error marshalling success response:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.WriteMessage(websocket.TextMessage, marshal)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error writing success response:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,45 +1,60 @@
|
|||||||
document.addEventListener("dragover", function (event) {
|
if (!window.mySocket) {
|
||||||
event.preventDefault();
|
window.mySocket = new WebSocket(`ws://${window.location.host}/user`);
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("drop", async function (event) {
|
window.mySocket.onopen = function(event) {
|
||||||
event.preventDefault();
|
console.log('WebSocket is open now.');
|
||||||
const file = event.dataTransfer.files[0]
|
};
|
||||||
await handleFile(file)
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('dropzone-file').addEventListener('change', async function(event) {
|
window.mySocket.onmessage = async function(event) {
|
||||||
event.preventDefault();
|
try {
|
||||||
const file = event.target.files[0]
|
const data = JSON.parse(event.data);
|
||||||
await handleFile(file)
|
if (data.action === "UploadNewFile") {
|
||||||
});
|
if (data.response.Done === false) {
|
||||||
|
const file = window.fileIdMap[data.responseID];
|
||||||
|
addNewUploadElement(file);
|
||||||
|
const fileChunks = await splitFile(file, file.chunkSize);
|
||||||
|
await uploadChunks(file.name, file.size, fileChunks, data.response.Chunk, data.response.ID);
|
||||||
|
} else {
|
||||||
|
alert("File already uploaded.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing message data:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.mySocket.onerror = function(event) {
|
||||||
|
console.error('WebSocket error observed:', event);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.mySocket.onclose = function(event) {
|
||||||
|
console.log('WebSocket is closed now.');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateUniqueId() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||||
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function handleFile(file){
|
async function handleFile(file){
|
||||||
const chunkSize = 2 * 1024 * 1024;
|
const chunkSize = 2 * 1024 * 1024;
|
||||||
const chunks = Math.ceil(file.size / chunkSize);
|
const chunks = Math.ceil(file.size / chunkSize);
|
||||||
|
const fileId = generateUniqueId();
|
||||||
const data = JSON.stringify({
|
const data = JSON.stringify({
|
||||||
|
"action": "UploadNewFile",
|
||||||
"name": file.name,
|
"name": file.name,
|
||||||
"size": file.size,
|
"size": file.size,
|
||||||
"chunk": chunks,
|
"chunk": chunks,
|
||||||
|
"requestID": fileId,
|
||||||
});
|
});
|
||||||
|
file.chunkSize = chunkSize;
|
||||||
|
window.fileIdMap = window.fileIdMap || {};
|
||||||
|
window.fileIdMap[fileId] = file;
|
||||||
|
|
||||||
fetch('/upload/init', {
|
window.mySocket.send(data)
|
||||||
method: 'POST',
|
|
||||||
body: data,
|
|
||||||
}).then(async response => {
|
|
||||||
const responseData = await response.json()
|
|
||||||
console.log(responseData)
|
|
||||||
if (responseData.Done === false) {
|
|
||||||
addNewUploadElement(file)
|
|
||||||
const fileChunks = await splitFile(file, chunkSize);
|
|
||||||
await uploadChunks(file.name,file.size, fileChunks, responseData.Chunk, responseData.ID);
|
|
||||||
} else {
|
|
||||||
alert("file already uploaded")
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('Error uploading file:', error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewUploadElement(file){
|
function addNewUploadElement(file){
|
@ -15,7 +15,6 @@ import (
|
|||||||
signupHandler "github.com/fossyy/filekeeper/handler/signup"
|
signupHandler "github.com/fossyy/filekeeper/handler/signup"
|
||||||
signupVerifyHandler "github.com/fossyy/filekeeper/handler/signup/verify"
|
signupVerifyHandler "github.com/fossyy/filekeeper/handler/signup/verify"
|
||||||
uploadHandler "github.com/fossyy/filekeeper/handler/upload"
|
uploadHandler "github.com/fossyy/filekeeper/handler/upload"
|
||||||
"github.com/fossyy/filekeeper/handler/upload/initialisation"
|
|
||||||
userHandler "github.com/fossyy/filekeeper/handler/user"
|
userHandler "github.com/fossyy/filekeeper/handler/user"
|
||||||
userHandlerResetPassword "github.com/fossyy/filekeeper/handler/user/ResetPassword"
|
userHandlerResetPassword "github.com/fossyy/filekeeper/handler/user/ResetPassword"
|
||||||
userSessionTerminateHandler "github.com/fossyy/filekeeper/handler/user/session/terminate"
|
userSessionTerminateHandler "github.com/fossyy/filekeeper/handler/user/session/terminate"
|
||||||
@ -118,10 +117,6 @@ func SetupRoutes() *http.ServeMux {
|
|||||||
middleware.Auth(uploadHandler.POST, w, r)
|
middleware.Auth(uploadHandler.POST, w, r)
|
||||||
})
|
})
|
||||||
|
|
||||||
handler.HandleFunc("POST /upload/init", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
middleware.Auth(initialisation.POST, w, r)
|
|
||||||
})
|
|
||||||
|
|
||||||
handler.HandleFunc("GET /download", func(w http.ResponseWriter, r *http.Request) {
|
handler.HandleFunc("GET /download", func(w http.ResponseWriter, r *http.Request) {
|
||||||
middleware.Auth(downloadHandler.GET, w, r)
|
middleware.Auth(downloadHandler.GET, w, r)
|
||||||
})
|
})
|
||||||
|
@ -14,7 +14,7 @@ type File struct {
|
|||||||
ID uuid.UUID `gorm:"primaryKey;not null;unique"`
|
ID uuid.UUID `gorm:"primaryKey;not null;unique"`
|
||||||
OwnerID uuid.UUID `gorm:"not null"`
|
OwnerID uuid.UUID `gorm:"not null"`
|
||||||
Name string `gorm:"not null"`
|
Name string `gorm:"not null"`
|
||||||
Size int64 `gorm:"not null"`
|
Size uint64 `gorm:"not null"`
|
||||||
TotalChunk int64 `gorm:"not null"`
|
TotalChunk uint64 `gorm:"not null"`
|
||||||
Downloaded int64 `gorm:"not null;default=0"`
|
Downloaded uint64 `gorm:"not null;default=0"`
|
||||||
}
|
}
|
||||||
|
@ -22,23 +22,23 @@ type User struct {
|
|||||||
|
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Size int64 `json:"size"`
|
Size uint64 `json:"size"`
|
||||||
Chunk int64 `json:"chunk"`
|
Chunk uint64 `json:"chunk"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileData struct {
|
type FileData struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Size string
|
Size string
|
||||||
Downloaded int64
|
Downloaded uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileWithDetail struct {
|
type FileWithDetail struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
OwnerID uuid.UUID
|
OwnerID uuid.UUID
|
||||||
Name string
|
Name string
|
||||||
Size int64
|
Size uint64
|
||||||
Downloaded int64
|
Downloaded uint64
|
||||||
Chunk map[string]bool
|
Chunk map[string]bool
|
||||||
Done bool
|
Done bool
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ func ValidatePassword(password string) bool {
|
|||||||
return hasNumber && hasUppercase
|
return hasNumber && hasUppercase
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertFileSize(byte int64) string {
|
func ConvertFileSize(byte uint64) string {
|
||||||
if byte < 1024 {
|
if byte < 1024 {
|
||||||
return fmt.Sprintf("%d B", byte)
|
return fmt.Sprintf("%d B", byte)
|
||||||
} else if byte < 1024*1024 {
|
} else if byte < 1024*1024 {
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
templ component(title string, files []types.FileData){
|
templ component(title string, files []types.FileData){
|
||||||
@layout.Base(title){
|
@layout.BaseAuth(title){
|
||||||
<div class="dark min-h-screen p-4 sm:p-6 bg-gray-900 text-white">
|
<div class="dark min-h-screen p-4 sm:p-6 bg-gray-900 text-white">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<header class="text-center">
|
<header class="text-center">
|
||||||
|
@ -23,6 +23,28 @@ templ Base(title string){
|
|||||||
</html>
|
</html>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templ BaseAuth(title string){
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="Secure and reliable file hosting service. Upload, organize, and share your documents, images, videos, and more. Sign up now to keep your files always within reach." />
|
||||||
|
<meta name="keywords" content="file hosting, file sharing, cloud storage, data storage, secure file hosting, filekeeper, drive, mega" />
|
||||||
|
<meta name="author" content="Filekeeper" />
|
||||||
|
<link href="/public/output.css" rel="stylesheet"/>
|
||||||
|
<title>{ title }</title>
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
||||||
|
<script src="/public/main.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
{ children... }
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
|
|
||||||
templ Navbar(user types.User) {
|
templ Navbar(user types.User) {
|
||||||
<header class="flex items-center justify-between border-b border-gray-200 bg-white px-6 py-4">
|
<header class="flex items-center justify-between border-b border-gray-200 bg-white px-6 py-4">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
|
@ -3,7 +3,7 @@ package uploadView
|
|||||||
import "github.com/fossyy/filekeeper/view/client/layout"
|
import "github.com/fossyy/filekeeper/view/client/layout"
|
||||||
|
|
||||||
templ content(title string) {
|
templ content(title string) {
|
||||||
@layout.Base(title){
|
@layout.BaseAuth(title){
|
||||||
<div class="flex items-center min-h-screen p-4 sm:p-6 bg-gray-900 text-white">
|
<div class="flex items-center min-h-screen p-4 sm:p-6 bg-gray-900 text-white">
|
||||||
<div class="mx-auto w-full max-w-md space-y-8">
|
<div class="mx-auto w-full max-w-md space-y-8">
|
||||||
<div class="rounded-lg border bg-card text-card-foreground shadow-sm w-full max-w-md" data-v0-t="card">
|
<div class="rounded-lg border bg-card text-card-foreground shadow-sm w-full max-w-md" data-v0-t="card">
|
||||||
@ -43,7 +43,23 @@ templ content(title string) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/public/upload.js" />
|
<script type="text/javascript">
|
||||||
|
document.addEventListener("dragover", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("drop", async function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const file = event.dataTransfer.files[0]
|
||||||
|
await handleFile(file)
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('dropzone-file').addEventListener('change', async function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const file = event.target.files[0]
|
||||||
|
await handleFile(file)
|
||||||
|
});
|
||||||
|
</script>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
templ content(title string, qrcode string, code string, user types.User, msg types.Message) {
|
templ content(title string, qrcode string, code string, user types.User, msg types.Message) {
|
||||||
@layout.Base(title){
|
@layout.BaseAuth(title){
|
||||||
@layout.Navbar(user)
|
@layout.Navbar(user)
|
||||||
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
|
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
|
||||||
<a
|
<a
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
templ content(message types.Message, title string, user types.User, ListSession []*session.SessionInfo) {
|
templ content(message types.Message, title string, user types.User, ListSession []*session.SessionInfo) {
|
||||||
@layout.Base(title){
|
@layout.BaseAuth(title){
|
||||||
@layout.Navbar(user)
|
@layout.Navbar(user)
|
||||||
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
|
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
|
||||||
<div class="grid gap-10 lg:grid-cols-[1fr_300px]">
|
<div class="grid gap-10 lg:grid-cols-[1fr_300px]">
|
||||||
|
Reference in New Issue
Block a user