Skip to content

Commit

Permalink
feat: Add file and folder sharing
Browse files Browse the repository at this point in the history
  • Loading branch information
divyam234 committed Sep 15, 2024
1 parent a37ac52 commit ad6e0cc
Show file tree
Hide file tree
Showing 15 changed files with 530 additions and 34 deletions.
18 changes: 15 additions & 3 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ func InitRouter(r *gin.Engine, c *controller.Controller, cnf *config.Config, db
files.HEAD(":fileID/download/:fileName", c.GetFileDownload)
files.GET(":fileID/download/:fileName", c.GetFileDownload)
files.PUT(":fileID/parts", authmiddleware, c.UpdateParts)
files.POST(":fileID/share", authmiddleware, c.CreateShare)
files.GET(":fileID/share", authmiddleware, c.GetShareByFileId)
files.PATCH(":fileID/share", authmiddleware, c.EditShare)
files.DELETE(":fileID/share", authmiddleware, c.DeleteShare)
files.GET("/category/stats", authmiddleware, c.GetCategoryStats)
files.POST("/move", authmiddleware, c.MoveFiles)
files.POST("/directories", authmiddleware, c.MakeDirectory)
Expand All @@ -44,9 +48,9 @@ func InitRouter(r *gin.Engine, c *controller.Controller, cnf *config.Config, db
{
uploads.Use(authmiddleware)
uploads.GET("/stats", c.UploadStats)
uploads.GET(":id", c.GetUploadFileById)
uploads.POST(":id", c.UploadFile)
uploads.DELETE(":id", c.DeleteUploadFile)
uploads.GET("/:id", c.GetUploadFileById)
uploads.POST("/:id", c.UploadFile)
uploads.DELETE("/:id", c.DeleteUploadFile)
}
users := api.Group("/users")
{
Expand All @@ -60,6 +64,14 @@ func InitRouter(r *gin.Engine, c *controller.Controller, cnf *config.Config, db
users.DELETE("/bots", c.RemoveBots)
users.DELETE("/sessions/:id", c.RemoveSession)
}
share := api.Group("/share")
{
share.GET("/:shareID", c.GetShareById)
share.GET("/:shareID/files", c.ListShareFiles)
share.GET("/:shareID/files/:fileID/stream/:fileName", c.StreamSharedFile)
share.GET("/:shareID/files/:fileID/download/:fileName", c.StreamSharedFile)
share.POST("/:shareID/unlock", c.ShareUnlock)
}
}

ui.AddRoutes(r)
Expand Down
1 change: 1 addition & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ func runApplication(conf *config.Config) {
services.NewFileService,
services.NewUploadService,
services.NewUserService,
services.NewShareService,
controller.NewController,
),
fx.Invoke(
Expand Down
18 changes: 18 additions & 0 deletions internal/database/migrations/20240912164825_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS teldrive.file_shares (
id uuid NOT NULL DEFAULT gen_random_uuid(),
file_id uuid NOT NULL,
password text NULL,
expires_at timestamp NULL,
created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
updated_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
user_id bigint NOT NULL,
CONSTRAINT file_shares_pkey PRIMARY KEY (id),
CONSTRAINT fk_file FOREIGN KEY (file_id) REFERENCES teldrive.files (id) ON DELETE CASCADE
);

CREATE INDEX idx_file_shares_file_id ON teldrive.file_shares USING btree (file_id);
ALTER TABLE teldrive.files DROP COLUMN IF EXISTS starred;

-- +goose StatementEnd
46 changes: 46 additions & 0 deletions internal/database/migrations/20240915100057_modify.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
-- +goose Up
-- +goose StatementBegin
DROP INDEX IF EXISTS teldrive.idx_files_starred_updated_at;
CREATE OR REPLACE FUNCTION teldrive.create_directories(u_id bigint, long_path text)
RETURNS SETOF teldrive.files
LANGUAGE plpgsql
AS $function$
DECLARE
path_parts TEXT[];
current_directory_id UUID;
new_directory_id UUID;
directory_name TEXT;
path_so_far TEXT;
BEGIN
path_parts := string_to_array(regexp_replace(long_path, '^/+', ''), '/');

path_so_far := '';

SELECT id INTO current_directory_id
FROM teldrive.files
WHERE parent_id is NULL AND user_id = u_id AND type = 'folder';

FOR directory_name IN SELECT unnest(path_parts) LOOP
path_so_far := CONCAT(path_so_far, '/', directory_name);

SELECT id INTO new_directory_id
FROM teldrive.files
WHERE parent_id = current_directory_id
AND "name" = directory_name
AND "user_id" = u_id;

IF new_directory_id IS NULL THEN
INSERT INTO teldrive.files ("name", "type", mime_type, parent_id, "user_id")
VALUES (directory_name, 'folder', 'drive/folder', current_directory_id, u_id)
RETURNING id INTO new_directory_id;
END IF;

current_directory_id := new_directory_id;
END LOOP;

RETURN QUERY SELECT * FROM teldrive.files WHERE id = current_directory_id;
END;
$function$
;
-- +goose StatementEnd

4 changes: 4 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ func Int64Pointer(b int64) *int64 {
return &b
}

func StringPointer(b string) *string {
return &b
}

func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
Expand Down
5 changes: 4 additions & 1 deletion pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ type Controller struct {
UserService *services.UserService
UploadService *services.UploadService
AuthService *services.AuthService
ShareService *services.ShareService
}

func NewController(fileService *services.FileService,
userService *services.UserService,
uploadService *services.UploadService,
authService *services.AuthService) *Controller {
authService *services.AuthService,
shareService *services.ShareService) *Controller {
return &Controller{
FileService: fileService,
UserService: userService,
UploadService: uploadService,
AuthService: authService,
ShareService: shareService,
}
}
68 changes: 66 additions & 2 deletions pkg/controller/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,70 @@ func (fc *Controller) DeleteFiles(c *gin.Context) {
c.JSON(http.StatusOK, res)
}

func (fc *Controller) CreateShare(c *gin.Context) {

userId, _ := auth.GetUser(c)

var payload schemas.FileShareIn
if err := c.ShouldBindJSON(&payload); err != nil {
httputil.NewError(c, http.StatusBadRequest, err)
return
}

err := fc.FileService.CreateShare(c.Param("fileID"), userId, &payload)
if err != nil {
httputil.NewError(c, err.Code, err.Error)
return
}

c.Status(http.StatusCreated)
}

func (fc *Controller) EditShare(c *gin.Context) {

userId, _ := auth.GetUser(c)

var payload schemas.FileShareIn
if err := c.ShouldBindJSON(&payload); err != nil {
httputil.NewError(c, http.StatusBadRequest, err)
return
}

err := fc.FileService.UpdateShare(c.Param("shareID"), userId, &payload)
if err != nil {
httputil.NewError(c, err.Code, err.Error)
return
}

c.Status(http.StatusOK)
}

func (fc *Controller) DeleteShare(c *gin.Context) {

userId, _ := auth.GetUser(c)

err := fc.FileService.DeleteShare(c.Param("fileID"), userId)
if err != nil {
httputil.NewError(c, err.Code, err.Error)
return
}

c.Status(http.StatusNoContent)
}

func (fc *Controller) GetShareByFileId(c *gin.Context) {

userId, _ := auth.GetUser(c)

res, err := fc.FileService.GetShareByFileId(c.Param("fileID"), userId)
if err != nil {
httputil.NewError(c, err.Code, err.Error)
return
}

c.JSON(http.StatusOK, res)
}

func (fc *Controller) UpdateParts(c *gin.Context) {

userId, _ := auth.GetUser(c)
Expand Down Expand Up @@ -196,9 +260,9 @@ func (fc *Controller) GetCategoryStats(c *gin.Context) {
}

func (fc *Controller) GetFileStream(c *gin.Context) {
fc.FileService.GetFileStream(c, false)
fc.FileService.GetFileStream(c, false, nil)
}

func (fc *Controller) GetFileDownload(c *gin.Context) {
fc.FileService.GetFileStream(c, true)
fc.FileService.GetFileStream(c, true, nil)
}
66 changes: 66 additions & 0 deletions pkg/controller/share.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package controller

import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/tgdrive/teldrive/pkg/httputil"
"github.com/tgdrive/teldrive/pkg/schemas"
)

func (sc *Controller) GetShareById(c *gin.Context) {

res, err := sc.ShareService.GetShareById(c.Param("shareID"))
if err != nil {
httputil.NewError(c, err.Code, err.Error)
return
}

c.JSON(http.StatusOK, res)
}

func (sc *Controller) ShareUnlock(c *gin.Context) {
var payload schemas.ShareAccess
if err := c.ShouldBindJSON(&payload); err != nil {
httputil.NewError(c, http.StatusBadRequest, err)
return
}
err := sc.ShareService.ShareUnlock(c.Param("shareID"), &payload)
if err != nil {
httputil.NewError(c, err.Code, err.Error)
return
}

c.Status(http.StatusOK)
}

func (sc *Controller) ListShareFiles(c *gin.Context) {

query := schemas.ShareFileQuery{
Limit: 500,
Page: 1,
Order: "asc",
Sort: "name",
}

if err := c.ShouldBindQuery(&query); err != nil {
httputil.NewError(c, http.StatusBadRequest, err)
return
}

res, err := sc.ShareService.ListShareFiles(c.Param("shareID"), &query, c.GetHeader("Authorization"))
if err != nil {
httputil.NewError(c, err.Code, err.Error)
return
}

c.JSON(http.StatusOK, res)
}

func (sc *Controller) StreamSharedFile(c *gin.Context) {
sc.ShareService.StreamSharedFile(c, false)
}

func (sc *Controller) DownloadSharedFile(c *gin.Context) {
sc.ShareService.StreamSharedFile(c, true)
}
1 change: 0 additions & 1 deletion pkg/mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ func ToFileOut(file models.File) *schemas.FileOut {
Category: file.Category,
Encrypted: file.Encrypted,
Size: size,
Starred: file.Starred,
ParentID: file.ParentID.String,
UpdatedAt: file.UpdatedAt,
}
Expand Down
1 change: 0 additions & 1 deletion pkg/models/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ type File struct {
Type string `gorm:"type:text;not null"`
MimeType string `gorm:"type:text;not null"`
Size *int64 `gorm:"type:bigint"`
Starred bool `gorm:"default:false"`
Category string `gorm:"type:text"`
Encrypted bool `gorm:"default:false"`
UserID int64 `gorm:"type:bigint;not null"`
Expand Down
15 changes: 15 additions & 0 deletions pkg/models/share.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package models

import (
"time"
)

type FileShare struct {
ID string `gorm:"type:uuid;default:uuid_generate_v4();primary_key"`
FileID string `gorm:"type:uuid;not null"`
Password *string `gorm:"type:text"`
ExpiresAt *time.Time `gorm:"type:timestamp"`
CreatedAt time.Time `gorm:"type:timestamp;not null;default:current_timestamp"`
UpdatedAt time.Time `gorm:"type:timestamp;not null;default:current_timestamp"`
UserID int64 `gorm:"type:bigint;not null"`
}
24 changes: 21 additions & 3 deletions pkg/schemas/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type FileQuery struct {
Path string `form:"path"`
Op string `form:"op"`
DeepSearch bool `form:"deepSearch"`
Starred *bool `form:"starred"`
Shared *bool `form:"shared"`
ParentID string `form:"parentId"`
Category string `form:"category"`
UpdatedAt string `form:"updatedAt"`
Expand Down Expand Up @@ -46,7 +46,6 @@ type FileOut struct {
Category string `json:"category,omitempty"`
Encrypted bool `json:"encrypted"`
Size int64 `json:"size,omitempty"`
Starred bool `json:"starred"`
ParentID string `json:"parentId,omitempty"`
ParentPath string `json:"parentPath,omitempty"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
Expand All @@ -61,7 +60,6 @@ type FileOutFull struct {

type FileUpdate struct {
Name string `json:"name,omitempty"`
Starred *bool `json:"starred,omitempty"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
Parts []Part `json:"parts,omitempty"`
Size *int64 `json:"size,omitempty"`
Expand Down Expand Up @@ -112,3 +110,23 @@ type FileCategoryStats struct {
TotalSize int `json:"totalSize"`
Category string `json:"category"`
}

type FileShareIn struct {
Password string `json:"password,omitempty"`
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
}

type FileShareOut struct {
ID string `json:"id"`
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
Protected bool `json:"protected"`
UserID int64 `json:"userId,omitempty"`
}

type FileShare struct {
Password *string
ExpiresAt *time.Time
Type string
FileId string
UserId int64
}
13 changes: 13 additions & 0 deletions pkg/schemas/share.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package schemas

type ShareAccess struct {
Password string `json:"password" binding:"required"`
}

type ShareFileQuery struct {
ParentID string `form:"parentId"`
Sort string `form:"sort"`
Order string `form:"order"`
Limit int `form:"limit"`
Page int `form:"page"`
}
Loading

0 comments on commit ad6e0cc

Please sign in to comment.