├── .env ├── .github └── workflows │ └── ghr-image-bp.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── SECURITY.md ├── controllers └── files.go ├── crontab └── crontab.go ├── database └── db.go ├── doc └── openapi.json ├── docker-compose.yaml ├── go.mod ├── go.sum ├── main.go ├── readme.md └── utils ├── file.go ├── multipart.go └── port.go /.env: -------------------------------------------------------------------------------- 1 | BACKEND_PORT="5000" 2 | JWT_SECRET="simplesecret@@" 3 | 4 | DB_TYPE="sqlite" 5 | 6 | DB_HOST = 'localhost' 7 | DB_PORT = '5432' 8 | DB_NAME = 'tempfilesdb' 9 | DB_USER = 'postgres' 10 | DB_PASSWORD = 'simplesecret@@' 11 | -------------------------------------------------------------------------------- /.github/workflows/ghr-image-bp.yml: -------------------------------------------------------------------------------- 1 | name: BUILD and PUSH to GHR 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: ["main"] 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | REGISTRY_IMAGE: ghcr.io/${{ github.repository }} 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | packages: write 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Set up QEMU 25 | uses: docker/setup-qemu-action@v3 26 | 27 | - name: Set up Docker Buildx 28 | uses: docker/setup-buildx-action@v3 29 | 30 | - name: Log in to the Container registry 31 | uses: docker/login-action@v3 32 | with: 33 | registry: ${{ env.REGISTRY }} 34 | username: ${{ github.actor }} 35 | password: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - name: Build and push with Buildx 38 | uses: docker/build-push-action@v5 39 | with: 40 | context: . 41 | file: ./Dockerfile 42 | platforms: linux/arm64, linux/amd64 43 | push: true 44 | tags: ${{ env.REGISTRY_IMAGE }}:${{ github.sha }}, ${{ env.REGISTRY_IMAGE }}:${{ github.ref_name }} 45 | cache-from: type=gha 46 | cache-to: type=gha,mode=min 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tempfiles-backend 2 | *.db 3 | tmp 4 | composeTmp -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Step 1: Modules caching 2 | FROM --platform=$BUILDPLATFORM golang:1.22.1-alpine as modules 3 | COPY go.mod go.sum /modules/ 4 | WORKDIR /modules 5 | RUN go mod download 6 | 7 | # Step 2: Builder 8 | FROM --platform=$BUILDPLATFORM golang:1.22.1-alpine AS builder 9 | COPY --from=modules /go/pkg /go/pkg 10 | COPY . /app 11 | ENV CGO_ENABLED=0 12 | WORKDIR /app 13 | ARG TARGETOS TARGETARCH 14 | ENV CGO_ENABLED=0 15 | RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /app/server . 16 | 17 | # GOPATH for scratch images is / 18 | FROM alpine 19 | WORKDIR /app 20 | COPY --from=builder /app/server /app/server 21 | EXPOSE 5000 22 | CMD ./server -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 tempfiles-backend 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /controllers/files.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "mime" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "path/filepath" 12 | "strconv" 13 | "strings" 14 | "time" 15 | 16 | "github.com/go-fuego/fuego" 17 | "github.com/tempfiles-Team/tempfiles-backend/database" 18 | "github.com/tempfiles-Team/tempfiles-backend/utils" 19 | ) 20 | 21 | type FilesRessources struct { 22 | // TODO add ressources 23 | FilesService RealFilesService 24 | } 25 | 26 | type File struct { 27 | database.FileTracking 28 | 29 | Error string `json:"error"` 30 | Message string `json:"message"` 31 | } 32 | 33 | type Files struct { 34 | List []database.FileTracking `json:"list"` 35 | 36 | Error string `json:"error"` 37 | Message string `json:"message"` 38 | } 39 | 40 | type FilesCreate struct { 41 | Message string `json:"message"` 42 | } 43 | 44 | func (rs FilesRessources) RoutesV1(s *fuego.Server) { 45 | fuego.Get(s, "/list", rs.getAllFiles) 46 | 47 | fuego.Post(s, "/upload", rs.postFiles). 48 | Description("Upload files"). 49 | Header("X-Download-Limit", "Download limit"). 50 | Header("X-Time-Limit", "Time limit"). 51 | Header("X-Hidden", "Hidden") 52 | 53 | fuego.GetStd(s, "/dl/{id}/{name}", rs.downloadFile) 54 | fuego.GetStd(s, "/view/{id}/{name}", rs.ViewFile) 55 | fuego.Get(s, "/file/{id}", rs.getFiles) 56 | fuego.Delete(s, "/del/{id}", rs.deleteFiles) 57 | } 58 | 59 | func (rs FilesRessources) getAllFiles(c fuego.ContextNoBody) (Files, error) { 60 | return rs.FilesService.GetAllFiles() 61 | } 62 | 63 | func (rs FilesRessources) postFiles(c *fuego.ContextWithBody[any]) (File, error) { 64 | 65 | return rs.FilesService.CreateFiles(c) 66 | } 67 | 68 | func (rs FilesRessources) getFiles(c fuego.ContextNoBody) (File, error) { 69 | return rs.FilesService.GetFiles(c.PathParam("id")) 70 | } 71 | 72 | func (rs FilesRessources) deleteFiles(c *fuego.ContextNoBody) (File, error) { 73 | return rs.FilesService.DeleteFiles(c.PathParam("id")) 74 | } 75 | 76 | func (rs FilesRessources) downloadFile(w http.ResponseWriter, r *http.Request) { 77 | id := r.PathValue("id") 78 | name := r.PathValue("name") 79 | 80 | path, err := rs.FilesService.DownloadFile(id, name) 81 | 82 | if err != nil { 83 | http.Error(w, err.Error(), http.StatusInternalServerError) 84 | return 85 | } 86 | 87 | w.Header().Set("Content-Disposition", "attachment; filename="+strings.ReplaceAll(url.PathEscape(name), "+", "%20")) 88 | http.ServeFile(w, r, path) 89 | } 90 | 91 | func (rs FilesRessources) ViewFile(w http.ResponseWriter, r *http.Request) { 92 | id := r.PathValue("id") 93 | name := r.PathValue("name") 94 | 95 | path, err := rs.FilesService.DownloadFile(id, name) 96 | 97 | if err != nil { 98 | http.Error(w, err.Error(), http.StatusInternalServerError) 99 | return 100 | } 101 | mimeType := mime.TypeByExtension(filepath.Ext(name)) 102 | 103 | // 만약 텍스트 파일인 경우에 (코드 및 기타 텍스트 파일) 파일을 열어서 plain text로 보여줍니다. 104 | if strings.Contains(mimeType, "text") { 105 | file, err := os.Open(path) 106 | if err != nil { 107 | http.Error(w, err.Error(), http.StatusInternalServerError) 108 | return 109 | } 110 | 111 | defer file.Close() 112 | w.Header().Set("Content-Type", mimeType) 113 | // file를 읽어서 response에 쓰기 114 | if _, err := io.Copy(w, file); err != nil { 115 | http.Error(w, err.Error(), http.StatusInternalServerError) 116 | return 117 | } 118 | 119 | } else { 120 | http.ServeFile(w, r, path) 121 | } 122 | 123 | } 124 | 125 | type FilesService interface { 126 | GetFiles(id string) (File, error) 127 | CreateFiles(FilesCreate) (File, error) 128 | GetAllFiles() (Files, error) 129 | DeleteFiles(id string) (any, error) 130 | DownloadFile(id string, name string) (File, error) 131 | } 132 | 133 | type RealFilesService struct { 134 | FilesService 135 | } 136 | 137 | func (s RealFilesService) GetFiles(id string) (File, error) { 138 | 139 | FileTracking := database.FileTracking{ 140 | FolderId: id, 141 | } 142 | 143 | has, err := database.Engine.Get(&FileTracking) 144 | 145 | if err != nil { 146 | return File{ 147 | Message: "db query error", 148 | }, err 149 | } 150 | 151 | if !has { 152 | return File{ 153 | Message: "file not found", 154 | }, nil 155 | } 156 | 157 | if files, err := utils.GetFiles(FileTracking.FolderId); err != nil { 158 | return File{ 159 | Message: "folder not found", 160 | }, nil 161 | } else { 162 | log.Println("✨ File found: ", FileTracking.FolderId) 163 | 164 | FileTracking.Files = files 165 | 166 | repons := File{ 167 | Message: "File found", 168 | } 169 | 170 | repons.FileTracking = FileTracking 171 | 172 | return repons, nil 173 | } 174 | } 175 | 176 | func (s RealFilesService) DownloadFile(id string, name string) (path string, error error) { 177 | 178 | FileTracking := database.FileTracking{ 179 | FolderId: id, 180 | } 181 | 182 | has, err := database.Engine.Get(&FileTracking) 183 | 184 | if err != nil { 185 | return "", err 186 | } 187 | 188 | if !has { 189 | return "", fmt.Errorf("folder not found") 190 | } 191 | 192 | if !utils.CheckIsFileExist(FileTracking.FolderId, name) { 193 | return "", fmt.Errorf("file not found") 194 | } 195 | 196 | // db DownloadCount +1 197 | FileTracking.DownloadCount++ 198 | if _, err := database.Engine.ID(FileTracking.Id).Update(&FileTracking); err != nil { 199 | 200 | return "", err 201 | } 202 | 203 | if FileTracking.DownloadLimit != 0 && FileTracking.DownloadCount >= FileTracking.DownloadLimit { 204 | 205 | FileTracking.IsDeleted = true 206 | 207 | log.Printf("🗑️ Set this folder for deletion: %s \n", FileTracking.FolderId) 208 | if _, err := database.Engine.ID(FileTracking.Id).Cols("Is_deleted").Update(&FileTracking); err != nil { 209 | 210 | return "", err 211 | } 212 | } 213 | 214 | log.Printf("📥️ Successfully downloaded %s, %s\n", FileTracking.FolderId, name) 215 | 216 | return "tmp/" + FileTracking.FolderId + "/" + name, nil 217 | } 218 | 219 | func (s RealFilesService) CreateFiles(c *fuego.ContextWithBody[any]) (File, error) { 220 | 221 | err := c.Request().ParseMultipartForm(10 << 20) // limit file size to 10MB 222 | if err != nil { 223 | return File{ 224 | Message: fmt.Sprintf("Error parsing file: %v", err), 225 | }, nil 226 | } 227 | 228 | isHidden, err := strconv.ParseBool(c.Header("X-Hidden")) 229 | if err != nil { 230 | isHidden = false 231 | } 232 | 233 | downloadLimit, err := strconv.Atoi(c.Header("X-Download-Limit")) 234 | if err != nil { 235 | downloadLimit = 100 236 | } 237 | expireTime, err := strconv.Atoi(c.Header("X-Time-Limit")) 238 | 239 | var expireTimeDate time.Time 240 | 241 | if err != nil || expireTime <= 0 { 242 | expireTimeDate = time.Now().Add(time.Duration(60*3) * time.Minute) 243 | } else { 244 | expireTimeDate = time.Now().Add(time.Duration(expireTime) * time.Minute) 245 | } 246 | 247 | // Multipart File And Header 248 | MFAHASH, err := utils.FormFiles(c.Request(), "file") 249 | if err != nil { 250 | return File{ 251 | Message: fmt.Sprintf("Please send the file using the “file” field in multipart/form-data.: %v", err), 252 | }, nil 253 | } 254 | 255 | FolderHash, err := utils.GenIdFormMulitpart(MFAHASH) 256 | if err != nil { 257 | return File{ 258 | Message: fmt.Sprintf("folder id generation error: %v", err), 259 | }, nil 260 | } 261 | 262 | isExist, err := database.Engine.Exist(&database.FileTracking{FolderHash: FolderHash}) 263 | 264 | if err != nil { 265 | return File{ 266 | Error: err.Error(), 267 | Message: fmt.Sprintf("database exist error: %v", err), 268 | }, nil 269 | } 270 | 271 | if isExist { 272 | FileTracking := database.FileTracking{ 273 | FolderHash: FolderHash, 274 | } 275 | _, err := database.Engine.Get(&FileTracking) 276 | if err != nil { 277 | 278 | return File{ 279 | Error: err.Error(), 280 | Message: fmt.Sprintf("database get error: %v", err), 281 | }, nil 282 | } 283 | 284 | FileTracking.ExpireTime = expireTimeDate.Unix() 285 | if _, err := database.Engine.ID(FileTracking.Id).Cols("expire_time").Update(&FileTracking); err != nil { 286 | resp := File{ 287 | Error: err.Error(), 288 | Message: fmt.Sprintf("database update error: %v", err), 289 | } 290 | 291 | resp.FileTracking = FileTracking 292 | 293 | return resp, nil 294 | } 295 | 296 | FileTracking.DownloadCount = 0 297 | if _, err := database.Engine.ID(FileTracking.Id).Cols("download_count").Update(&FileTracking); err != nil { 298 | resp := File{ 299 | Error: err.Error(), 300 | Message: fmt.Sprintf("database update error: %v", err), 301 | } 302 | 303 | resp.FileTracking = FileTracking 304 | 305 | return resp, nil 306 | } 307 | 308 | resp := File{ 309 | Message: fmt.Sprintf("File %s already exists, reset time limit and download count.", FileTracking.FolderHash[:5]), 310 | } 311 | 312 | resp.FileTracking = FileTracking 313 | 314 | return resp, nil 315 | } 316 | 317 | MFAH, err := utils.FormFiles(c.Request(), "file") 318 | if err != nil { 319 | return File{ 320 | Message: fmt.Sprintf("Please send the file using the “file” field in multipart/form-data.: %v", err), 321 | }, nil 322 | } 323 | 324 | filesList := make([]database.FileListResponse, 0) 325 | for _, file := range MFAH { 326 | filesList = append(filesList, database.FileListResponse{ 327 | FileName: file.Header.Filename, 328 | FileSize: file.Header.Size, 329 | }) 330 | } 331 | 332 | FileTracking := &database.FileTracking{ 333 | FileCount: len(MFAH), 334 | FolderId: FolderHash[:5], 335 | IsHidden: isHidden, 336 | FolderHash: FolderHash, 337 | UploadDate: time.Now(), 338 | DownloadLimit: int64(downloadLimit), 339 | ExpireTime: expireTimeDate.Unix(), 340 | Files: filesList, 341 | } 342 | 343 | if utils.CheckFileFolder(FileTracking.FolderId) != nil { 344 | 345 | return File{ 346 | Error: err.Error(), 347 | Message: fmt.Sprintf("file folder duplication error: %v", err), 348 | }, nil 349 | 350 | } 351 | 352 | for _, file := range MFAH { 353 | 354 | if err := utils.SaveFile(FileTracking.FolderId, file.Header.Filename, file.File); err != nil { 355 | 356 | return File{ 357 | Error: err.Error(), 358 | Message: fmt.Sprintf("file save error: %v", err), 359 | }, nil 360 | } 361 | } 362 | 363 | _, err = database.Engine.Insert(FileTracking) 364 | if err != nil { 365 | 366 | return File{ 367 | Error: err.Error(), 368 | Message: fmt.Sprintf("database insert error: %v", err), 369 | }, nil 370 | } 371 | 372 | log.Printf("🥰 Successfully uploaded %s, %d files\n", FileTracking.FolderId, FileTracking.FileCount) 373 | 374 | resp := File{ 375 | Message: fmt.Sprintf("File %s uploaded successfully", FileTracking.FolderHash[:5]), 376 | } 377 | 378 | resp.FileTracking = *FileTracking 379 | 380 | return resp, nil 381 | } 382 | 383 | func (s RealFilesService) GetAllFiles() (Files, error) { 384 | // TODO implement 385 | 386 | var files []database.FileTracking 387 | 388 | if err := database.Engine.Where("is_deleted = ? AND is_hidden = ?", false, false).Find(&files); err != nil { 389 | return Files{ 390 | Message: "db query error", 391 | Error: err.Error(), 392 | }, nil 393 | } 394 | 395 | return Files{ 396 | List: files, 397 | Message: "File list successfully", 398 | }, nil 399 | } 400 | 401 | func (s RealFilesService) DeleteFiles(id string) (File, error) { 402 | FileTracking := database.FileTracking{ 403 | FolderId: id, 404 | } 405 | 406 | has, err := database.Engine.Get(&FileTracking) 407 | 408 | if err != nil { 409 | return File{ 410 | Message: "db query error", 411 | Error: err.Error(), 412 | }, nil 413 | } 414 | 415 | if !has { 416 | return File{ 417 | Message: "file not found", 418 | Error: "true", 419 | }, nil 420 | } 421 | 422 | if err := os.RemoveAll("tmp/" + FileTracking.FolderId); err != nil { 423 | 424 | return File{ 425 | Message: "file delete error", 426 | Error: err.Error(), 427 | }, nil 428 | } 429 | 430 | if _, err := database.Engine.Delete(&FileTracking); err != nil { 431 | 432 | return File{ 433 | Message: "db delete error", 434 | Error: err.Error(), 435 | }, nil 436 | } 437 | 438 | log.Printf("🗑️ Delete this folder: %s\n", FileTracking.FolderId) 439 | 440 | return File{ 441 | Message: "File deleted successfully", 442 | }, nil 443 | } 444 | -------------------------------------------------------------------------------- /crontab/crontab.go: -------------------------------------------------------------------------------- 1 | package crontab 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | 8 | "github.com/robfig/cron" 9 | "github.com/tempfiles-Team/tempfiles-backend/database" 10 | ) 11 | 12 | func Crontab() { 13 | terminator := cron.New() 14 | 15 | terminator.AddFunc("@every 30s", func() { 16 | log.Println("⏲️ Check for expired files", time.Now().Format("2006-01-02 15:04:05")) 17 | var files []database.FileTracking 18 | if err := database.Engine.Where("expire_time < ? and is_deleted = ?", time.Now().Unix(), false).Find(&files); err != nil { 19 | log.Println("cron db query error", err.Error()) 20 | } 21 | for _, file := range files { 22 | log.Printf("🗑️ Set this folder for deletion: %s \n", file.FolderId) 23 | file.IsDeleted = true 24 | if _, err := database.Engine.ID(file.Id).Cols("Is_deleted").Update(&file); err != nil { 25 | log.Printf("cron db update error, file: %s, error: %s\n", file.FolderId, err.Error()) 26 | } 27 | } 28 | }) 29 | 30 | terminator.AddFunc("@every 1m", func() { 31 | log.Println("⏲️ Check which files need to be deleted", time.Now().Format("2006-01-02 15:04:05")) 32 | var files []database.FileTracking 33 | if err := database.Engine.Where("is_deleted = ?", true).Find(&files); err != nil { 34 | log.Println("file list error: ", err.Error()) 35 | } 36 | for _, file := range files { 37 | log.Printf("🗑️ Delete this folder: %s\n", file.FolderId) 38 | if err := os.RemoveAll("./tmp/" + file.FolderId); err != nil { 39 | log.Println("delete file error: ", err.Error()) 40 | } 41 | if _, err := database.Engine.Delete(&file); err != nil { 42 | log.Println("delete file error: ", err.Error()) 43 | } 44 | } 45 | }) 46 | 47 | terminator.Start() 48 | } 49 | -------------------------------------------------------------------------------- /database/db.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | _ "github.com/lib/pq" 8 | _ "modernc.org/sqlite" 9 | 10 | "time" 11 | 12 | "xorm.io/xorm" 13 | ) 14 | 15 | type FileListResponse struct { 16 | FileName string `json:"fileName"` 17 | FileSize int64 `json:"fileSize"` 18 | DownloadUrl string `json:"downloadUrl"` 19 | } 20 | 21 | type FileTracking struct { 22 | Id int64 `json:"-"` 23 | FolderHash string `json:"-"` 24 | IsDeleted bool `json:"-"` 25 | 26 | IsHidden bool `json:"isHidden"` 27 | FolderId string `json:"folderId"` 28 | FileCount int `json:"fileCount"` 29 | DownloadCount int64 `json:"downloadCount"` 30 | DownloadLimit int64 `json:"downloadLimit"` 31 | UploadDate time.Time `json:"uploadDate"` 32 | ExpireTime int64 `json:"expireTime"` 33 | 34 | Files []FileListResponse `json:"files"` 35 | } 36 | 37 | var Engine *xorm.Engine 38 | 39 | func CreateDBEngine() error { 40 | var err error 41 | if os.Getenv("DB_TYPE") == "sqlite" { 42 | Engine, err = xorm.NewEngine("sqlite", "tmp/data.db") 43 | } else { 44 | connectionInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", 45 | os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME")) 46 | Engine, err = xorm.NewEngine("postgres", connectionInfo) 47 | } 48 | 49 | if err != nil { 50 | return err 51 | } 52 | 53 | Engine.SetMaxIdleConns(1) 54 | Engine.SetMaxOpenConns(1) 55 | Engine.SetConnMaxLifetime(time.Minute * 5) 56 | 57 | if err := Engine.Ping(); err != nil { 58 | return err 59 | } 60 | if err := Engine.Sync(new(FileTracking)); err != nil { 61 | return err 62 | } 63 | 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /doc/openapi.json: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","components":{"schemas":{"File":{"properties":{"downloadCount":{"format":"int64","type":"integer"},"downloadLimit":{"format":"int64","type":"integer"},"error":{"type":"string"},"expireTime":{"format":"int64","type":"integer"},"fileCount":{"type":"integer"},"files":{"items":{"properties":{"downloadUrl":{"type":"string"},"fileName":{"type":"string"},"fileSize":{"format":"int64","type":"integer"}},"type":"object"},"type":"array"},"folderId":{"type":"string"},"isHidden":{"type":"boolean"},"message":{"type":"string"},"uploadDate":{"format":"date-time","type":"string"}},"type":"object"},"Files":{"properties":{"error":{"type":"string"},"list":{"items":{"properties":{"downloadCount":{"format":"int64","type":"integer"},"downloadLimit":{"format":"int64","type":"integer"},"expireTime":{"format":"int64","type":"integer"},"fileCount":{"type":"integer"},"files":{"items":{"properties":{"downloadUrl":{"type":"string"},"fileName":{"type":"string"},"fileSize":{"format":"int64","type":"integer"}},"type":"object"},"type":"array"},"folderId":{"type":"string"},"isHidden":{"type":"boolean"},"uploadDate":{"format":"date-time","type":"string"}},"type":"object"},"type":"array"},"message":{"type":"string"}},"type":"object"},"string":{"type":"string"},"unknown-interface":{}}},"info":{"description":"OpenAPI","title":"OpenAPI","version":"0.0.1"},"paths":{"/":{"get":{"description":"controller: main.main.func1","operationId":"GET /:func1","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/string"}}},"description":"OK"},"default":{"description":""}},"summary":"func1","tags":["default"]}},"/del/{id}":{"delete":{"description":"controller: github.com/tempfiles-Team/tempfiles-backend/controllers.FilesRessources.deleteFiles","operationId":"DELETE /del/{id}:deleteFiles","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}},"description":"OK"},"default":{"description":""}},"summary":"deleteFiles","tags":["File"]}},"/dl/{id}/{name}":{"get":{"description":"controller: github.com/tempfiles-Team/tempfiles-backend/controllers.FilesRessources.downloadFile","operationId":"GET /dl/{id}/{name}:downloadFile","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/unknown-interface"}}},"description":"OK"},"default":{"description":""}},"summary":"downloadFile"}},"/file/{id}":{"get":{"description":"controller: github.com/tempfiles-Team/tempfiles-backend/controllers.FilesRessources.getFiles","operationId":"GET /file/{id}:getFiles","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}},"description":"OK"},"default":{"description":""}},"summary":"getFiles","tags":["File"]}},"/list":{"get":{"description":"controller: github.com/tempfiles-Team/tempfiles-backend/controllers.FilesRessources.getAllFiles","operationId":"GET /list:getAllFiles","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Files"}}},"description":"OK"},"default":{"description":""}},"summary":"getAllFiles","tags":["Files"]}},"/upload":{"post":{"description":"Upload files","operationId":"POST /upload:postFiles","parameters":[{"description":"Download limit","in":"header","name":"X-Download-Limit","schema":{"type":"string"}},{"description":"Time limit","in":"header","name":"X-Time-Limit","schema":{"type":"string"}},{"description":"Hidden","in":"header","name":"X-Hidden","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}},"description":"OK"},"default":{"description":""}},"summary":"postFiles","tags":["File"]}},"/view/{id}/{name}":{"get":{"description":"controller: github.com/tempfiles-Team/tempfiles-backend/controllers.FilesRessources.ViewFile","operationId":"GET /view/{id}/{name}:ViewFile","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/unknown-interface"}}},"description":"OK"},"default":{"description":""}},"summary":"ViewFile"}}}} -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | tempfiles-backend: 4 | container_name: tempfiles-backend 5 | build: . 6 | restart: unless-stopped 7 | ports: 8 | - "5000:5000" 9 | environment: 10 | - BACKEND_PORT=5000 11 | - JWT_SECRET=simplesecret 12 | - DB_TYPE=sqlite 13 | volumes: 14 | - tempfiles-tmp:/tmp 15 | 16 | volumes: 17 | tempfiles-tmp: -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tempfiles-Team/tempfiles-backend 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.1 6 | 7 | require ( 8 | github.com/go-fuego/fuego v0.13.4 9 | github.com/joho/godotenv v1.5.1 10 | github.com/lib/pq v1.10.9 11 | github.com/robfig/cron v1.2.0 12 | github.com/rs/cors v1.10.1 13 | modernc.org/sqlite v1.29.8 14 | xorm.io/xorm v1.3.9 15 | ) 16 | 17 | require ( 18 | github.com/dustin/go-humanize v1.0.1 // indirect 19 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 20 | github.com/getkin/kin-openapi v0.124.0 // indirect 21 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 22 | github.com/go-openapi/swag v0.23.0 // indirect 23 | github.com/go-playground/locales v0.14.1 // indirect 24 | github.com/go-playground/universal-translator v0.18.1 // indirect 25 | github.com/go-playground/validator/v10 v10.19.0 // indirect 26 | github.com/goccy/go-json v0.10.2 // indirect 27 | github.com/golang-jwt/jwt/v5 v5.2.1 // indirect 28 | github.com/golang/snappy v0.0.4 // indirect 29 | github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 // indirect 30 | github.com/google/uuid v1.6.0 // indirect 31 | github.com/gorilla/schema v1.3.0 // indirect 32 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 33 | github.com/invopop/yaml v0.3.1 // indirect 34 | github.com/josharian/intern v1.0.0 // indirect 35 | github.com/json-iterator/go v1.1.12 // indirect 36 | github.com/leodido/go-urn v1.4.0 // indirect 37 | github.com/mailru/easyjson v0.7.7 // indirect 38 | github.com/mattn/go-isatty v0.0.20 // indirect 39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 40 | github.com/modern-go/reflect2 v1.0.2 // indirect 41 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 42 | github.com/ncruces/go-strftime v0.1.9 // indirect 43 | github.com/perimeterx/marshmallow v1.1.5 // indirect 44 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 45 | github.com/syndtr/goleveldb v1.0.0 // indirect 46 | github.com/ugorji/go/codec v1.2.11 // indirect 47 | golang.org/x/crypto v0.22.0 // indirect 48 | golang.org/x/net v0.24.0 // indirect 49 | golang.org/x/sys v0.19.0 // indirect 50 | golang.org/x/text v0.14.0 // indirect 51 | gopkg.in/yaml.v2 v2.4.0 // indirect 52 | gopkg.in/yaml.v3 v3.0.1 // indirect 53 | modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect 54 | modernc.org/libc v1.50.2 // indirect 55 | modernc.org/mathutil v1.6.0 // indirect 56 | modernc.org/memory v1.8.0 // indirect 57 | modernc.org/strutil v1.2.0 // indirect 58 | modernc.org/token v1.1.0 // indirect 59 | xorm.io/builder v0.3.13 // indirect 60 | ) 61 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= 2 | gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 7 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 8 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 9 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 10 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 11 | github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= 12 | github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= 13 | github.com/go-fuego/fuego v0.13.4 h1:tIDUH9ZKd8UTftkjtFnKhSYPo41NLhH1GxLsHBPZxII= 14 | github.com/go-fuego/fuego v0.13.4/go.mod h1:Haw/N+HVuU0mMTjgpPXOWsQ8DsvBJDWSiJCblEh/7P4= 15 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 16 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 17 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 18 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 19 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 20 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 21 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 22 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 23 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 24 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 25 | github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= 26 | github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 27 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 28 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 29 | github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= 30 | github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 31 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 32 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 33 | github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 34 | github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 35 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 36 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 37 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 38 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 39 | github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 h1:yEt5djSYb4iNtmV9iJGVday+i4e9u6Mrn5iP64HH5QM= 40 | github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= 41 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 42 | github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= 43 | github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= 44 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 45 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 46 | github.com/gorilla/schema v1.3.0 h1:rbciOzXAx3IB8stEFnfTwO3sYa6EWlQk79XdyustPDA= 47 | github.com/gorilla/schema v1.3.0/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= 48 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 49 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 50 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 51 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 52 | github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= 53 | github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= 54 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 55 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 56 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 57 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 58 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 59 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 60 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 61 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 62 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 63 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 64 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 65 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 66 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 67 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 68 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 69 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 70 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 71 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 72 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 73 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 74 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 75 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 76 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 77 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 78 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 79 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 80 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 81 | github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= 82 | github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= 83 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 84 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= 85 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 86 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= 87 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 88 | github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= 89 | github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= 90 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 91 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 92 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 93 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 94 | github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= 95 | github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= 96 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 97 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 98 | github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= 99 | github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= 100 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 101 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 102 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 103 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 104 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= 105 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 106 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 107 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 108 | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= 109 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 110 | golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= 111 | golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 112 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 113 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 114 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 115 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 116 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 117 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 118 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 119 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 120 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 121 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 122 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 123 | golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= 124 | golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= 125 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 126 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 127 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 128 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 129 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 130 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 131 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 132 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 133 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 134 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 135 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 136 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 137 | modernc.org/cc/v4 v4.21.0 h1:D/gLKtcztomvWbsbvBKo3leKQv+86f+DdqEZBBXhnag= 138 | modernc.org/cc/v4 v4.21.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= 139 | modernc.org/ccgo/v4 v4.17.0 h1:cX97L5Bv/7PEmyk1oEAD890fQu5/yUQRYeYBsCSnzww= 140 | modernc.org/ccgo/v4 v4.17.0/go.mod h1:keES1eiOIBJhbA5qKrV7ADG3w8DsX8G7jfHAT76riOg= 141 | modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= 142 | modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= 143 | modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= 144 | modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= 145 | modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8= 146 | modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= 147 | modernc.org/libc v1.50.2 h1:I0+3wlRvXmAEjAJvD7BhP1kmKHwkzV0rOcqFcD85u+0= 148 | modernc.org/libc v1.50.2/go.mod h1:Fd8TZdfRorOd1vB0QCtYSHYAuzobS4xS3mhMGUkeVcA= 149 | modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= 150 | modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= 151 | modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= 152 | modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= 153 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= 154 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 155 | modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= 156 | modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= 157 | modernc.org/sqlite v1.29.8 h1:nGKglNx9K5v0As+zF0/Gcl1kMkmaU1XynYyq92PbsC8= 158 | modernc.org/sqlite v1.29.8/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk= 159 | modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= 160 | modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= 161 | modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= 162 | modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 163 | xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo= 164 | xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= 165 | xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU= 166 | xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw= 167 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/go-fuego/fuego" 9 | "github.com/rs/cors" 10 | 11 | _ "github.com/joho/godotenv/autoload" 12 | controller "github.com/tempfiles-Team/tempfiles-backend/controllers" 13 | "github.com/tempfiles-Team/tempfiles-backend/crontab" 14 | "github.com/tempfiles-Team/tempfiles-backend/database" 15 | "github.com/tempfiles-Team/tempfiles-backend/utils" 16 | ) 17 | 18 | func main() { 19 | 20 | crontab.Crontab() 21 | 22 | if err := utils.CheckTmpFolder(); err != nil { 23 | log.Fatalf("tmp folder error: %v", err) 24 | } 25 | 26 | if err := database.CreateDBEngine(); err != nil { 27 | log.Fatalf("failed to create db engine: %v", err) 28 | } 29 | 30 | // ======================== SERVER ======================== 31 | 32 | if os.Getenv("BACKEND_PORT") == "" { 33 | os.Setenv("BACKEND_PORT", "5000") 34 | } 35 | port := utils.CheckPortAvailable(os.Getenv("BACKEND_PORT")) 36 | 37 | s := fuego.NewServer( 38 | fuego.WithAddr("0.0.0.0:"+port), 39 | fuego.WithCorsMiddleware(cors.New(cors.Options{ 40 | AllowedOrigins: []string{"*"}, 41 | AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, 42 | AllowedHeaders: []string{"Origin", "Content-Type", "Accept", "X-Download-Limit", "X-Time-Limit", "X-Hidden"}, 43 | }).Handler), 44 | ) 45 | 46 | fuego.Get(s, "/", func(c fuego.ContextNoBody) (string, error) { 47 | return "TEMPFILES API WORKING 🚀\nIf you want to use the API, go to '/swagger'", nil 48 | }).Tags("default") 49 | 50 | v1wv := fuego.Group(s, "/") 51 | controller.FilesRessources{}.RoutesV1(v1wv) 52 | 53 | s.Run() 54 | } 55 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## tempfiles-backend 2 | 3 | frontend - https://github.com/tempfiles-Team/tempfiles-frontend 4 | 5 | tempfiles-backend is a backend for tempfiles-frontend. 6 | 7 | ## How to run - docker 8 | 9 | ### 1. build docker image 10 | 11 | ```bash 12 | docker build -t tempfiles-backend . 13 | ``` 14 | 15 | or pull image from docker hub 16 | 17 | ```bash 18 | docker pull minpeter/tempfiles-backend 19 | ``` 20 | 21 | ### 2. run docker container 22 | 23 | ```bash 24 | docker run -dp 5000:5000 \ 25 | -e JWT_SECRET= \ 26 | -e DB_TYPE=sqlite \ 27 | -v $(pwd)/backend-data:/tmp \ 28 | tempfiles-backend 29 | ``` 30 | 31 | ## How to run - local 32 | 33 | 1. config .env file 34 | 35 | ### nessary 36 | 37 | | key | value | description | 38 | | :--------: | :----------------: | :---------: | 39 | | JWT_SECRET | | jwt secret | 40 | | DB_TYPE | sqlite or postgres | select db | 41 | 42 | ### optional 43 | 44 | | key | value | description | 45 | | :----------: | :-------: | :--------------------------------------------: | 46 | | BACKEND_PORT | 5000 | If you want to change the backend port | 47 | | DB_HOST | localhost | If postgres is selected, its db ip or hostname | 48 | | DB_PORT | 5432 | If postgres is selected, its db port | 49 | | DB_NAME | tempdb | If postgres is selected, its db table name | 50 | | DB_USER | tempdb | If postgres is selected, its db user name | 51 | | DB_PASSWORD | tempdb | If postgres is selected, its db user password | 52 | 53 | 2. run server 54 | 55 | ```bash 56 | go run . 57 | ``` 58 | 59 | ## test server 60 | 61 | https://api.tmpf.me 62 | 63 | - 파일 목록 조회 [Get] 64 | https://api.tmpf.me/list 65 | 66 | - 파일 업로드 [Post] 67 | multipart/form-data "file" 필드에 업로드할 파일을 넣어서 요청 68 | https://api.tmpf.me/upload 69 | 70 | - 파일 다운로드 [Get] 71 | https://api.tmpf.me/dl/(file_id) 72 | 73 | - 파일 삭제 [Delete] 74 | https://api.tmpf.me/del/(file_id) 75 | -------------------------------------------------------------------------------- /utils/file.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | "mime/multipart" 6 | "net/url" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/tempfiles-Team/tempfiles-backend/database" 12 | ) 13 | 14 | func CheckIsFileExist(folderId, fileName string) bool { 15 | if _, err := os.Stat("tmp/" + folderId + "/" + fileName); os.IsNotExist(err) { 16 | return false 17 | } 18 | return true 19 | } 20 | 21 | func GetFiles(folderId string) ([]database.FileListResponse, error) { 22 | // return filenames, file sizes 23 | var files []database.FileListResponse 24 | 25 | err := filepath.Walk("tmp/"+folderId, func(path string, info os.FileInfo, err error) error { 26 | if path != "tmp/"+folderId { 27 | files = append(files, database.FileListResponse{ 28 | FileName: filepath.Base(path), 29 | FileSize: info.Size(), 30 | DownloadUrl: "/dl/" + folderId + "/" + strings.ReplaceAll(url.PathEscape(filepath.Base(path)), "+", "%20"), 31 | }) 32 | } 33 | return nil 34 | }) 35 | return files, err 36 | 37 | } 38 | 39 | func SaveFile(folderId, fileName string, file multipart.File) error { 40 | // tmpf/debug/filename write 41 | os.MkdirAll("tmp/"+folderId, os.ModePerm) 42 | tmpf, err := os.Create("tmp/" + folderId + "/" + fileName) 43 | 44 | if err != nil { 45 | return err 46 | } 47 | 48 | defer tmpf.Close() 49 | 50 | _, err = io.Copy(tmpf, file) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | return nil 56 | 57 | } 58 | 59 | func CheckTmpFolder() error { 60 | if _, err := os.Stat("tmp"); os.IsNotExist(err) { 61 | err := os.Mkdir("tmp", 0755) 62 | if err != nil { 63 | return err 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | func CheckFileFolder(folderId string) error { 70 | if _, err := os.Stat("tmp/" + folderId); os.IsNotExist(err) { 71 | err := os.MkdirAll("tmp/"+folderId, 0755) 72 | if err != nil { 73 | return err 74 | } 75 | } 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /utils/multipart.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha1" 6 | "encoding/base64" 7 | "errors" 8 | "mime/multipart" 9 | "net/http" 10 | "sort" 11 | ) 12 | 13 | const ( 14 | defaultMaxMemory = 32 << 20 // 32 MB 15 | ) 16 | 17 | var multipartByReader = &multipart.Form{ 18 | Value: make(map[string][]string), 19 | File: make(map[string][]*multipart.FileHeader), 20 | } 21 | 22 | type MultiFileAndHeader struct { 23 | File multipart.File 24 | Header *multipart.FileHeader 25 | } 26 | 27 | // An improved version of net/http.Request.FormFile() 28 | func FormFiles(r *http.Request, key string) ([]MultiFileAndHeader, error) { 29 | 30 | if r.MultipartForm == multipartByReader { 31 | return nil, errors.New("http: multipart handled by MultipartReader") 32 | } 33 | if r.MultipartForm == nil { 34 | err := r.ParseMultipartForm(defaultMaxMemory) 35 | if err != nil { 36 | return nil, err 37 | } 38 | } 39 | 40 | if r.MultipartForm != nil && r.MultipartForm.File != nil { 41 | 42 | var files []MultiFileAndHeader 43 | if fhs := r.MultipartForm.File[key]; len(fhs) > 0 { 44 | 45 | for _, fh := range fhs { 46 | f, err := fh.Open() 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | files = append(files, MultiFileAndHeader{ 52 | File: f, 53 | Header: fh, 54 | }) 55 | } 56 | } 57 | 58 | if len(files) > 0 { 59 | return files, nil 60 | } 61 | } 62 | return nil, http.ErrMissingFile 63 | } 64 | 65 | func GenIdFormMulitpart(MFAH []MultiFileAndHeader) (string, error) { 66 | 67 | sort.Slice(MFAH, func(i, j int) bool { 68 | return MFAH[i].Header.Filename < MFAH[j].Header.Filename 69 | }) 70 | 71 | var hashes [][]byte 72 | 73 | for _, file := range MFAH { 74 | 75 | // defer file.File.Close() 76 | 77 | buf := new(bytes.Buffer) 78 | buf.ReadFrom(file.File) 79 | fileBytes := buf.Bytes() 80 | 81 | nameHash := sha1.Sum([]byte(file.Header.Filename)) 82 | hashes = append(hashes, nameHash[:]) 83 | fileHash := sha1.Sum(fileBytes) 84 | hashes = append(hashes, fileHash[:]) 85 | } 86 | 87 | combinedHash := sha1.Sum(bytes.Join(hashes, nil)) 88 | 89 | return base64.RawURLEncoding.EncodeToString(combinedHash[:]), nil 90 | } 91 | -------------------------------------------------------------------------------- /utils/port.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | ) 8 | 9 | func CheckPortAvailable(port string) string { 10 | // Parse the port number 11 | p, err := strconv.Atoi(port) 12 | if err != nil { 13 | return "" 14 | } 15 | 16 | // Check if the port is available 17 | addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", p)) 18 | if err != nil { 19 | return "" 20 | } 21 | 22 | l, err := net.ListenTCP("tcp", addr) 23 | if err != nil { 24 | // If the port is not available, increment the port number and try again 25 | return CheckPortAvailable(fmt.Sprintf("%d", p+1)) 26 | } 27 | defer l.Close() 28 | 29 | // If the port is available, return it 30 | 31 | fmt.Println("Port is available:", port) 32 | return port 33 | } 34 | --------------------------------------------------------------------------------