From 7fef0450bc4fa3ab8fd9d4dd21ee6031c12ca57c Mon Sep 17 00:00:00 2001 From: Marc Ole Bulling Date: Sun, 9 Jun 2024 19:33:23 +0200 Subject: [PATCH] Updated makefile, added tests, remove lastupdate database entry for uploadstatus --- .../configuration/configupgrade/Upgrade.go | 14 ++++++-- internal/configuration/database/Database.go | 1 - .../configuration/database/Database_test.go | 36 +++++++++++++------ .../configuration/database/uploadstatus.go | 12 +++---- internal/models/UploadStatus.go | 2 -- internal/models/UploadStatus_test.go | 2 +- .../processingstatus/ProcessingStatus.go | 2 -- .../processingstatus/ProcessingStatus_test.go | 1 - .../testconfiguration/TestConfiguration.go | 35 +++++++++++------- internal/webserver/sse/Sse_test.go | 2 +- internal/webserver/web/static/js/admin.js | 2 +- .../webserver/web/static/js/min/admin.min.js | 2 +- makefile | 6 ++-- 13 files changed, 71 insertions(+), 46 deletions(-) diff --git a/internal/configuration/configupgrade/Upgrade.go b/internal/configuration/configupgrade/Upgrade.go index 8282fb4..7a1a43f 100644 --- a/internal/configuration/configupgrade/Upgrade.go +++ b/internal/configuration/configupgrade/Upgrade.go @@ -11,7 +11,7 @@ import ( ) // CurrentConfigVersion is the version of the configuration structure. Used for upgrading -const CurrentConfigVersion = 19 +const CurrentConfigVersion = 20 // DoUpgrade checks if an old version is present and updates it to the current version if required func DoUpgrade(settings *models.Configuration, env *environment.Environment) bool { @@ -65,7 +65,7 @@ func updateConfig(settings *models.Configuration, env *environment.Environment) } settings.Authentication.OAuthRecheckInterval = 168 } - // < v1.8.5 + // < v1.8.5beta if settings.ConfigVersion < 19 { if settings.MaxMemory == 40 { settings.MaxMemory = 50 @@ -73,6 +73,16 @@ func updateConfig(settings *models.Configuration, env *environment.Environment) settings.ChunkSize = env.ChunkSizeMB settings.MaxParallelUploads = env.MaxParallelUploads } + // < v1.8.5 + if settings.ConfigVersion < 20 { + err := database.RawSqlite(`DROP TABLE UploadStatus; CREATE TABLE "UploadStatus" ( + "ChunkId" TEXT NOT NULL UNIQUE, + "CurrentStatus" INTEGER NOT NULL, + "CreationDate" INTEGER NOT NULL, + PRIMARY KEY("ChunkId") +) WITHOUT ROWID;`) + helper.Check(err) + } } // migrateToSqlite copies the content of the old bitcask database to a new sqlite database diff --git a/internal/configuration/database/Database.go b/internal/configuration/database/Database.go index 0bddbb4..6e35e1c 100644 --- a/internal/configuration/database/Database.go +++ b/internal/configuration/database/Database.go @@ -146,7 +146,6 @@ func createNewDatabase() { CREATE TABLE "UploadStatus" ( "ChunkId" TEXT NOT NULL UNIQUE, "CurrentStatus" INTEGER NOT NULL, - "LastUpdate" INTEGER NOT NULL, "CreationDate" INTEGER NOT NULL, PRIMARY KEY("ChunkId") ) WITHOUT ROWID; diff --git a/internal/configuration/database/Database_test.go b/internal/configuration/database/Database_test.go index eac5eb8..15caf90 100644 --- a/internal/configuration/database/Database_test.go +++ b/internal/configuration/database/Database_test.go @@ -216,54 +216,44 @@ func TestGarbageCollectionUploads(t *testing.T) { SaveUploadStatus(models.UploadStatus{ ChunkId: "ctodelete1", CurrentStatus: 0, - LastUpdate: time.Now().Add(-24 * time.Hour).Unix(), }) SaveUploadStatus(models.UploadStatus{ ChunkId: "ctodelete2", CurrentStatus: 1, - LastUpdate: time.Now().Add(-24 * time.Hour).Unix(), }) SaveUploadStatus(models.UploadStatus{ ChunkId: "ctodelete3", CurrentStatus: 0, - LastUpdate: 0, }) SaveUploadStatus(models.UploadStatus{ ChunkId: "ctodelete4", CurrentStatus: 0, - LastUpdate: time.Now().Add(-20 * time.Hour).Unix(), }) SaveUploadStatus(models.UploadStatus{ ChunkId: "ctodelete5", CurrentStatus: 1, - LastUpdate: time.Now().Add(40 * time.Hour).Unix(), }) currentTime = orgiginalFunc SaveUploadStatus(models.UploadStatus{ ChunkId: "ctokeep1", CurrentStatus: 0, - LastUpdate: time.Now().Add(-24 * time.Hour).Unix(), }) SaveUploadStatus(models.UploadStatus{ ChunkId: "ctokeep2", CurrentStatus: 1, - LastUpdate: time.Now().Add(-24 * time.Hour).Unix(), }) SaveUploadStatus(models.UploadStatus{ ChunkId: "ctokeep3", CurrentStatus: 0, - LastUpdate: 0, }) SaveUploadStatus(models.UploadStatus{ ChunkId: "ctokeep4", CurrentStatus: 0, - LastUpdate: time.Now().Add(-20 * time.Hour).Unix(), }) SaveUploadStatus(models.UploadStatus{ ChunkId: "ctokeep5", CurrentStatus: 1, - LastUpdate: time.Now().Add(40 * time.Hour).Unix(), }) for _, item := range []string{"ctodelete1", "ctodelete2", "ctodelete3", "ctodelete4", "ctokeep1", "ctokeep2", "ctokeep3", "ctokeep4"} { _, result := GetUploadStatus(item) @@ -463,3 +453,29 @@ func TestParallelConnectionsReading(t *testing.T) { } wg.Wait() } + +func TestUploadStatus(t *testing.T) { + allStatus := GetAllUploadStatus() + found := false + test.IsEqualInt(t, len(allStatus), 5) + for _, status := range allStatus { + if status.ChunkId == "ctokeep5" { + found = true + } + } + test.IsEqualBool(t, found, true) + newStatus := models.UploadStatus{ + ChunkId: "testid", + CurrentStatus: 1, + } + retrievedStatus, ok := GetUploadStatus("testid") + test.IsEqualBool(t, ok, false) + test.IsEqualBool(t, retrievedStatus == models.UploadStatus{}, true) + SaveUploadStatus(newStatus) + retrievedStatus, ok = GetUploadStatus("testid") + test.IsEqualBool(t, ok, true) + test.IsEqualString(t, retrievedStatus.ChunkId, "testid") + test.IsEqualInt(t, retrievedStatus.CurrentStatus, 1) + allStatus = GetAllUploadStatus() + test.IsEqualInt(t, len(allStatus), 6) +} diff --git a/internal/configuration/database/uploadstatus.go b/internal/configuration/database/uploadstatus.go index 91db70e..3bb6d7b 100644 --- a/internal/configuration/database/uploadstatus.go +++ b/internal/configuration/database/uploadstatus.go @@ -23,12 +23,11 @@ func GetAllUploadStatus() []models.UploadStatus { defer rows.Close() for rows.Next() { rowResult := schemaUploadStatus{} - err = rows.Scan(&rowResult.ChunkId, &rowResult.CurrentStatus, &rowResult.LastUpdate, &rowResult.CreationDate) + err = rows.Scan(&rowResult.ChunkId, &rowResult.CurrentStatus, &rowResult.CreationDate) helper.Check(err) result = append(result, models.UploadStatus{ ChunkId: rowResult.ChunkId, CurrentStatus: rowResult.CurrentStatus, - LastUpdate: rowResult.LastUpdate, }) } return result @@ -39,12 +38,11 @@ func GetUploadStatus(id string) (models.UploadStatus, bool) { result := models.UploadStatus{ ChunkId: id, CurrentStatus: 0, - LastUpdate: 0, } var rowResult schemaUploadStatus row := sqliteDb.QueryRow("SELECT * FROM UploadStatus WHERE ChunkId = ?", id) - err := row.Scan(&rowResult.ChunkId, &rowResult.CurrentStatus, &rowResult.LastUpdate, &rowResult.CreationDate) + err := row.Scan(&rowResult.ChunkId, &rowResult.CurrentStatus, &rowResult.CreationDate) if err != nil { if errors.Is(err, sql.ErrNoRows) { return models.UploadStatus{}, false @@ -53,7 +51,6 @@ func GetUploadStatus(id string) (models.UploadStatus, bool) { return models.UploadStatus{}, false } result.CurrentStatus = rowResult.CurrentStatus - result.LastUpdate = rowResult.LastUpdate return result, true } @@ -67,12 +64,11 @@ func SaveUploadStatus(status models.UploadStatus) { newData := schemaUploadStatus{ ChunkId: status.ChunkId, CurrentStatus: status.CurrentStatus, - LastUpdate: status.LastUpdate, CreationDate: currentTime().Unix(), } - _, err := sqliteDb.Exec("INSERT OR REPLACE INTO UploadStatus (ChunkId, CurrentStatus, LastUpdate, CreationDate) VALUES (?, ?, ?, ?)", - newData.ChunkId, newData.CurrentStatus, newData.LastUpdate, newData.CreationDate) + _, err := sqliteDb.Exec("INSERT OR REPLACE INTO UploadStatus (ChunkId, CurrentStatus, CreationDate) VALUES (?, ?, ?)", + newData.ChunkId, newData.CurrentStatus, newData.CreationDate) helper.Check(err) } diff --git a/internal/models/UploadStatus.go b/internal/models/UploadStatus.go index 7dcedb0..1e5fc2f 100644 --- a/internal/models/UploadStatus.go +++ b/internal/models/UploadStatus.go @@ -12,8 +12,6 @@ type UploadStatus struct { // hashing) or being moved/uploaded to the file storage // See processingstatus for definition CurrentStatus int `json:"currentstatus"` - // LastUpdate indicates the last status change - LastUpdate int64 `json:"lastupdate"` // Type is the type of the message and is always "uploadstatus" Type string `json:"type"` } diff --git a/internal/models/UploadStatus_test.go b/internal/models/UploadStatus_test.go index 8e56c32..ca0be83 100644 --- a/internal/models/UploadStatus_test.go +++ b/internal/models/UploadStatus_test.go @@ -9,5 +9,5 @@ func TestUploadStatus_ToJson(t *testing.T) { status := UploadStatus{} output, err := status.ToJson() test.IsNil(t, err) - test.IsEqualString(t, string(output), "{\"chunkid\":\"\",\"currentstatus\":0,\"lastupdate\":0,\"type\":\"uploadstatus\"}") + test.IsEqualString(t, string(output), "{\"chunkid\":\"\",\"currentstatus\":0,\"type\":\"uploadstatus\"}") } diff --git a/internal/storage/processingstatus/ProcessingStatus.go b/internal/storage/processingstatus/ProcessingStatus.go index 1b32eb3..7e18b6b 100644 --- a/internal/storage/processingstatus/ProcessingStatus.go +++ b/internal/storage/processingstatus/ProcessingStatus.go @@ -5,7 +5,6 @@ import ( "github.com/forceu/gokapi/internal/helper" "github.com/forceu/gokapi/internal/models" "github.com/forceu/gokapi/internal/webserver/sse" - "time" ) // StatusHashingOrEncrypting indicates that the file has been completely uploaded, but is now processed by Gokapi @@ -25,7 +24,6 @@ func Set(id string, status int) { newStatus := models.UploadStatus{ ChunkId: id, CurrentStatus: status, - LastUpdate: time.Now().Unix(), } oldStatus, ok := database.GetUploadStatus(newStatus.ChunkId) if ok && oldStatus.CurrentStatus > newStatus.CurrentStatus { diff --git a/internal/storage/processingstatus/ProcessingStatus_test.go b/internal/storage/processingstatus/ProcessingStatus_test.go index e9aabf1..49d9fb3 100644 --- a/internal/storage/processingstatus/ProcessingStatus_test.go +++ b/internal/storage/processingstatus/ProcessingStatus_test.go @@ -38,7 +38,6 @@ func TestSetStatus(t *testing.T) { initialStatus := models.UploadStatus{ ChunkId: chunkID, CurrentStatus: tc.initialStatus, - LastUpdate: time.Now().Unix(), } database.SaveUploadStatus(initialStatus) diff --git a/internal/test/testconfiguration/TestConfiguration.go b/internal/test/testconfiguration/TestConfiguration.go index f625e4e..ce3a477 100644 --- a/internal/test/testconfiguration/TestConfiguration.go +++ b/internal/test/testconfiguration/TestConfiguration.go @@ -11,6 +11,7 @@ import ( "github.com/forceu/gokapi/internal/storage/filesystem/s3filesystem/aws" "github.com/johannesboyne/gofakes3" "github.com/johannesboyne/gofakes3/backend/s3mem" + "log" "net/http/httptest" "os" "strings" @@ -172,20 +173,20 @@ func writeTestSessions() { }) } func writeTestUploadStatus() { - database.SaveUploadStatus(models.UploadStatus{ - ChunkId: "expiredstatus", - CurrentStatus: 0, - LastUpdate: 100, - }) + err := database.RawSqlite(`INSERT OR REPLACE INTO UploadStatus + ("ChunkId", "CurrentStatus", "CreationDate") + VALUES ('expiredstatus', 0, 100);`) + if err != nil { + log.Println(err) + log.Fatal("Could not execute SQL") + } database.SaveUploadStatus(models.UploadStatus{ ChunkId: "validstatus_0", CurrentStatus: 0, - LastUpdate: 2065000681, }) database.SaveUploadStatus(models.UploadStatus{ ChunkId: "validstatus_1", CurrentStatus: 1, - LastUpdate: 2065000681, }) } @@ -342,21 +343,31 @@ var configTestFile = []byte(`{ "Username": "test", "Password": "10340aece68aa4fb14507ae45b05506026f276cf", "HeaderKey": "", - "OAuthProvider": "", + "OauthProvider": "", "OAuthClientId": "", "OAuthClientSecret": "", + "OauthUserScope": "", + "OauthGroupScope": "", + "OAuthRecheckInterval": 12, "HeaderUsers": null, - "OAuthUsers": null + "OAuthGroups": [], + "OauthUsers": [] }, - "Port":"127.0.0.1:53843", + "Port":"127.0.0.1:53843", "ServerUrl": "http://127.0.0.1:53843/", "RedirectUrl": "https://test.com/", - "ConfigVersion": 16, + "PublicName": "Gokapi Test Version", + "ConfigVersion": 20, "LengthId": 20, "DataDir": "test/data", + "MaxFileSizeMB": 25, "MaxMemory": 10, + "ChunkSize": 45, + "MaxParallelUploads": 4, "UseSsl": false, - "MaxFileSizeMB": 25 + "PicturesAlwaysLocal": false, + "SaveIp": false, + "IncludeFilename": false }`) var sslCertValid = []byte(`-----BEGIN CERTIFICATE----- diff --git a/internal/webserver/sse/Sse_test.go b/internal/webserver/sse/Sse_test.go index 55d7266..6464dbf 100644 --- a/internal/webserver/sse/Sse_test.go +++ b/internal/webserver/sse/Sse_test.go @@ -91,7 +91,7 @@ func TestGetStatusSSE(t *testing.T) { body, err := io.ReadAll(rr.Body) test.IsNil(t, err) - test.IsEqualString(t, string(body), "{\"chunkid\":\"expiredstatus\",\"currentstatus\":0,\"lastupdate\":100,\"type\":\"uploadstatus\"}\n{\"chunkid\":\"validstatus_0\",\"currentstatus\":0,\"lastupdate\":2065000681,\"type\":\"uploadstatus\"}\n{\"chunkid\":\"validstatus_1\",\"currentstatus\":1,\"lastupdate\":2065000681,\"type\":\"uploadstatus\"}\n") + test.IsEqualString(t, string(body), "{\"chunkid\":\"expiredstatus\",\"currentstatus\":0,\"type\":\"uploadstatus\"}\n{\"chunkid\":\"validstatus_0\",\"currentstatus\":0,\"type\":\"uploadstatus\"}\n{\"chunkid\":\"validstatus_1\",\"currentstatus\":1,\"type\":\"uploadstatus\"}\n") // Test ping message time.Sleep(3 * time.Second) diff --git a/internal/webserver/web/static/js/admin.js b/internal/webserver/web/static/js/admin.js index 9942e6b..d89cc26 100644 --- a/internal/webserver/web/static/js/admin.js +++ b/internal/webserver/web/static/js/admin.js @@ -473,7 +473,7 @@ function registerChangeHandler() { console.log("Reconnecting to SSE..."); // Attempt to reconnect after a delay - setTimeout(registerChangeHandler, 1000); + setTimeout(registerChangeHandler, 5000); }; } diff --git a/internal/webserver/web/static/js/min/admin.min.js b/internal/webserver/web/static/js/min/admin.min.js index 1bf099c..a3ce8c7 100644 --- a/internal/webserver/web/static/js/min/admin.min.js +++ b/internal/webserver/web/static/js/min/admin.min.js @@ -1 +1 @@ -var clipboard=new ClipboardJS(".btn"),dropzoneObject,calendarInstance,statusItemCount,isE2EEnabled=!1,isUploading=!1,rowCount=-1;window.addEventListener("beforeunload",e=>{isUploading&&(e.returnValue="Upload is still in progress. Do you want to close this page?")}),Dropzone.options.uploaddropzone={paramName:"file",dictDefaultMessage:"Drop files, paste or click here to upload",createImageThumbnails:!1,chunksUploaded:function(e,t){sendChunkComplete(e,t)},init:function(){dropzoneObject=this,this.on("addedfile",e=>{addFileProgress(e)}),this.on("queuecomplete",function(){isUploading=!1}),this.on("sending",function(){isUploading=!0}),this.on("error",function(e,t,n){n&&n.status===413?showError(e,"File too large to upload. If you are using a reverse proxy, make sure that the allowed body size is at least 70MB."):showError(e,"Server responded with code "+n.status)}),this.on("uploadprogress",function(e,t,n){updateProgressbar(e,t,n)}),isE2EEnabled&&(dropzoneObject.disable(),dropzoneObject.options.dictDefaultMessage="Loading end-to-end encryption...",document.getElementsByClassName("dz-button")[0].innerText="Loading end-to-end encryption...",setE2eUpload())}};function updateProgressbar(e,t,n){let o=e.upload.uuid,i=document.getElementById(`us-container-${o}`);if(i==null||i.getAttribute("data-complete")==="true")return;let s=Math.round(t);s<0&&(s=0),s>100&&(s=100);let r=Date.now()-i.getAttribute("data-starttime"),c=n/(r/1e3)/1024/1024;document.getElementById(`us-progressbar-${o}`).style.width=s+"%";let a=Math.round(c*10)/10;Number.isNaN(a)||(document.getElementById(`us-progress-info-${o}`).innerText=s+"% - "+a+"MB/s")}function setProgressStatus(e,t){let s=document.getElementById(`us-container-${e}`);if(s==null)return;s.setAttribute("data-complete","true");let n;switch(t){case 0:n="Processing file...";break;case 1:n="Uploading file...";break}document.getElementById(`us-progress-info-${e}`).innerText=n}function addFileProgress(e){addFileStatus(e.upload.uuid,e.upload.filename)}document.onpaste=function(e){if(dropzoneObject.disabled)return;var t,n=(e.clipboardData||e.originalEvent.clipboardData).items;for(index in n)t=n[index],t.kind==="file"&&dropzoneObject.addFile(t.getAsFile()),t.kind==="string"&&t.getAsString(function(e){const t=//gi;if(t.test(e)===!1){let t=new Blob([e],{type:"text/plain"}),n=new File([t],"Pasted Text.txt",{type:"text/plain",lastModified:new Date(0)});dropzoneObject.addFile(n)}})};function urlencodeFormData(e){let t="";function s(e){return encodeURIComponent(e).replace(/%20/g,"+")}for(var n of e.entries())typeof n[1]=="string"&&(t+=(t?"&":"")+s(n[0])+"="+s(n[1]));return t}function sendChunkComplete(e,t){var s=new XMLHttpRequest;s.open("POST","./uploadComplete",!0),s.setRequestHeader("Content-Type","application/x-www-form-urlencoded");let n=new FormData;n.append("allowedDownloads",document.getElementById("allowedDownloads").value),n.append("expiryDays",document.getElementById("expiryDays").value),n.append("password",document.getElementById("password").value),n.append("isUnlimitedDownload",!document.getElementById("enableDownloadLimit").checked),n.append("isUnlimitedTime",!document.getElementById("enableTimeLimit").checked),n.append("chunkid",e.upload.uuid),e.isEndToEndEncrypted===!0?(n.append("filesize",e.sizeEncrypted),n.append("filename","Encrypted File"),n.append("filecontenttype",""),n.append("isE2E","true"),n.append("realSize",e.size)):(n.append("filesize",e.size),n.append("filename",e.name),n.append("filecontenttype",e.type)),s.onreadystatechange=function(){if(this.readyState==4)if(this.status==200){let n=addRow(s.response);if(e.isEndToEndEncrypted===!0){try{let s=GokapiE2EAddFile(e.upload.uuid,n,e.name);if(s instanceof Error)throw s;let t=GokapiE2EInfoEncrypt();if(t instanceof Error)throw t;storeE2EInfo(t)}catch(t){e.accepted=!1,dropzoneObject._errorProcessing([e],t);return}GokapiE2EDecryptMenu()}removeFileStatus(e.upload.uuid),t()}else{e.accepted=!1;let t=getErrorMessage(s.responseText);dropzoneObject._errorProcessing([e],t),showError(e,t)}},s.send(urlencodeFormData(n))}function getErrorMessage(e){let t;try{t=JSON.parse(e)}catch{return"Unknown error: Server could not process file"}return"Error: "+t.ErrorMessage}function showError(e,t){let n=e.upload.uuid;document.getElementById(`us-progressbar-${n}`).style.width="100%",document.getElementById(`us-progressbar-${n}`).style.backgroundColor="red",document.getElementById(`us-progress-info-${n}`).innerText=t,document.getElementById(`us-progress-info-${n}`).classList.add("uploaderror")}function editFile(){const e=document.getElementById("mb_save");e.disabled=!0;let i="./api/files/modify",n=document.getElementById("mi_edit_down").value,s=document.getElementById("mi_edit_expiry").value,t=document.getElementById("mi_edit_pw").value,o=t==="(unchanged)";document.getElementById("mc_download").checked||(n=0),document.getElementById("mc_expiry").checked||(s=0),document.getElementById("mc_password").checked||(o=!1,t="");const a={method:"PUT",headers:{"Content-Type":"application/json",id:e.getAttribute("data-fileid"),allowedDownloads:n,expiryTimestamp:s,password:t,originalPassword:o}};fetch(i,a).then(e=>{if(!e.ok)throw new Error(`Request failed with status: ${e.status}`)}).then(e=>{location.reload()}).catch(t=>{alert("Unable to edit file: "+t),console.error("Error:",t),e.disabled=!1})}calendarInstance=null;function createCalendar(e){const t=new Date(e*1e3);calendarInstance=flatpickr("#mi_edit_expiry",{enableTime:!0,dateFormat:"U",altInput:!0,altFormat:"Y-m-d H:i",allowInput:!0,time_24hr:!0,defaultDate:t,minDate:"today"})}function handleEditCheckboxChange(e){var t=document.getElementById(e.getAttribute("data-toggle-target")),n=e.getAttribute("data-timestamp");e.checked?(t.classList.remove("disabled"),t.removeAttribute("disabled"),n!=null&&(calendarInstance._input.disabled=!1)):(n!=null&&(calendarInstance._input.disabled=!0),t.classList.add("disabled"),t.setAttribute("disabled",!0))}function showEditModal(e,t,n,s,o,i,a){document.getElementById("m_filenamelabel").innerHTML=e,document.getElementById("mc_expiry").setAttribute("data-timestamp",s),document.getElementById("mb_save").setAttribute("data-fileid",t),createCalendar(s),i?(document.getElementById("mi_edit_down").value="1",document.getElementById("mi_edit_down").disabled=!0,document.getElementById("mc_download").checked=!1):(document.getElementById("mi_edit_down").value=n,document.getElementById("mi_edit_down").disabled=!1,document.getElementById("mc_download").checked=!0),a?(document.getElementById("mi_edit_expiry").value=add14DaysIfBeforeCurrentTime(s),document.getElementById("mi_edit_expiry").disabled=!0,document.getElementById("mc_expiry").checked=!1,calendarInstance._input.disabled=!0):(document.getElementById("mi_edit_expiry").value=s,document.getElementById("mi_edit_expiry").disabled=!1,document.getElementById("mc_expiry").checked=!0,calendarInstance._input.disabled=!1),o?(document.getElementById("mi_edit_pw").value="(unchanged)",document.getElementById("mi_edit_pw").disabled=!1,document.getElementById("mc_password").checked=!0):(document.getElementById("mi_edit_pw").value="",document.getElementById("mi_edit_pw").disabled=!0,document.getElementById("mc_password").checked=!1),new bootstrap.Modal("#modaledit",{}).show()}function selectTextForPw(e){e.value==="(unchanged)"&&e.setSelectionRange(0,e.value.length)}function add14DaysIfBeforeCurrentTime(e){let t=Date.now(),n=e*1e3;if(n{if(!e.ok)throw new Error(`Request failed with status: ${e.status}`)}).then(e=>{o?s.classList.add("apiperm-notgranted"):s.classList.add("apiperm-granted"),s.classList.remove("apiperm-processing")}).catch(e=>{o?s.classList.add("apiperm-granted"):s.classList.add("apiperm-notgranted"),s.classList.remove("apiperm-processing"),alert("Unable to set permission: "+e),console.error("Error:",e)})}function checkBoxChanged(e,t){let n=!e.checked;n?document.getElementById(t).setAttribute("disabled",""):document.getElementById(t).removeAttribute("disabled"),t==="password"&&n&&(document.getElementById("password").value="")}function parseData(e){return e?typeof e=="object"?e:typeof e=="string"?JSON.parse(e):{Result:"error"}:{Result:"error"}}function registerChangeHandler(){const e=new EventSource("./uploadStatus");e.onmessage=e=>{try{let t=JSON.parse(e.data);setProgressStatus(t.chunkid,t.currentstatus)}catch(e){console.error("Failed to parse event data:",e)}},e.onerror=t=>{t.target.readyState!==EventSource.CLOSED&&e.close(),console.log("Reconnecting to SSE..."),setTimeout(registerChangeHandler,1e3)}}statusItemCount=0;function addFileStatus(e,t){const n=document.createElement("div");n.setAttribute("id",`us-container-${e}`),n.classList.add("us-container");const a=document.createElement("div");a.classList.add("filename"),a.textContent=t,n.appendChild(a);const s=document.createElement("div");s.classList.add("upload-progress-container"),s.setAttribute("id",`us-progress-container-${e}`);const r=document.createElement("div");r.classList.add("upload-progress-bar");const o=document.createElement("div");o.setAttribute("id",`us-progressbar-${e}`),o.classList.add("upload-progress-bar-progress"),o.style.width="0%",r.appendChild(o);const i=document.createElement("div");i.setAttribute("id",`us-progress-info-${e}`),i.classList.add("upload-progress-info"),i.textContent="0%",s.appendChild(r),s.appendChild(i),n.appendChild(s),n.setAttribute("data-starttime",Date.now()),n.setAttribute("data-complete","false");const c=document.getElementById("uploadstatus");c.appendChild(n),c.style.visibility="visible",statusItemCount++}function removeFileStatus(e){const t=document.getElementById(`us-container-${e}`);if(t==null)return;t.remove(),statusItemCount--,statusItemCount<1&&(document.getElementById("uploadstatus").style.visibility="hidden")}function addRow(e){let r=parseData(e);if(r.Result!=="OK"){alert("Failed to upload file!"),location.reload();return}let t=r.FileInfo,p=document.getElementById("downloadtable"),s=p.insertRow(0),i=s.insertCell(0),a=s.insertCell(1),o=s.insertCell(2),c=s.insertCell(3),l=s.insertCell(4),d=s.insertCell(5),u=s.insertCell(6),h="";t.IsPasswordProtected===!0&&(h=' '),i.innerText=t.Name,i.id="cell-name-"+t.Id,a.innerText=t.Size,t.UnlimitedDownloads?o.innerText="Unlimited":o.innerText=t.DownloadsRemaining,t.UnlimitedTime?c.innerText="Unlimited":c.innerText=t.ExpireAtString,l.innerHTML="0",d.innerHTML=''+t.Id+""+h;let n=' ';t.UrlHotlink===""?n=n+' ':n=n+' ',n=n+' `,n=n+` ',n=n+``,u.innerHTML=n,i.style.backgroundColor="green",a.style.backgroundColor="green",a.setAttribute("data-order",r.FileInfo.SizeBytes),o.style.backgroundColor="green",c.style.backgroundColor="green",l.style.backgroundColor="green",d.style.backgroundColor="green",u.style.backgroundColor="green";let m=$("#maintable").DataTable();rowCount==-1&&(rowCount=m.rows().count()),rowCount=rowCount+1,m.row.add(s);let f=document.getElementsByClassName("dataTables_empty")[0];return typeof f!="undefined"?f.innerText="Files stored: "+rowCount:document.getElementsByClassName("dataTables_info")[0].innerText="Files stored: "+rowCount,t.Id}function hideQrCode(){document.getElementById("qroverlay").style.display="none",document.getElementById("qrcode").innerHTML=""}function showQrCode(e){const t=document.getElementById("qroverlay");t.style.display="block",new QRCode(document.getElementById("qrcode"),{text:e,width:200,height:200,colorDark:"#000000",colorLight:"#ffffff",correctLevel:QRCode.CorrectLevel.H}),t.addEventListener("click",hideQrCode)}function showToast(){let e=document.getElementById("toastnotification");e.classList.add("show"),setTimeout(()=>{e.classList.remove("show")},1e3)} \ No newline at end of file +var clipboard=new ClipboardJS(".btn"),dropzoneObject,calendarInstance,statusItemCount,isE2EEnabled=!1,isUploading=!1,rowCount=-1;window.addEventListener("beforeunload",e=>{isUploading&&(e.returnValue="Upload is still in progress. Do you want to close this page?")}),Dropzone.options.uploaddropzone={paramName:"file",dictDefaultMessage:"Drop files, paste or click here to upload",createImageThumbnails:!1,chunksUploaded:function(e,t){sendChunkComplete(e,t)},init:function(){dropzoneObject=this,this.on("addedfile",e=>{addFileProgress(e)}),this.on("queuecomplete",function(){isUploading=!1}),this.on("sending",function(){isUploading=!0}),this.on("error",function(e,t,n){n&&n.status===413?showError(e,"File too large to upload. If you are using a reverse proxy, make sure that the allowed body size is at least 70MB."):showError(e,"Server responded with code "+n.status)}),this.on("uploadprogress",function(e,t,n){updateProgressbar(e,t,n)}),isE2EEnabled&&(dropzoneObject.disable(),dropzoneObject.options.dictDefaultMessage="Loading end-to-end encryption...",document.getElementsByClassName("dz-button")[0].innerText="Loading end-to-end encryption...",setE2eUpload())}};function updateProgressbar(e,t,n){let o=e.upload.uuid,i=document.getElementById(`us-container-${o}`);if(i==null||i.getAttribute("data-complete")==="true")return;let s=Math.round(t);s<0&&(s=0),s>100&&(s=100);let r=Date.now()-i.getAttribute("data-starttime"),c=n/(r/1e3)/1024/1024;document.getElementById(`us-progressbar-${o}`).style.width=s+"%";let a=Math.round(c*10)/10;Number.isNaN(a)||(document.getElementById(`us-progress-info-${o}`).innerText=s+"% - "+a+"MB/s")}function setProgressStatus(e,t){let s=document.getElementById(`us-container-${e}`);if(s==null)return;s.setAttribute("data-complete","true");let n;switch(t){case 0:n="Processing file...";break;case 1:n="Uploading file...";break}document.getElementById(`us-progress-info-${e}`).innerText=n}function addFileProgress(e){addFileStatus(e.upload.uuid,e.upload.filename)}document.onpaste=function(e){if(dropzoneObject.disabled)return;var t,n=(e.clipboardData||e.originalEvent.clipboardData).items;for(index in n)t=n[index],t.kind==="file"&&dropzoneObject.addFile(t.getAsFile()),t.kind==="string"&&t.getAsString(function(e){const t=//gi;if(t.test(e)===!1){let t=new Blob([e],{type:"text/plain"}),n=new File([t],"Pasted Text.txt",{type:"text/plain",lastModified:new Date(0)});dropzoneObject.addFile(n)}})};function urlencodeFormData(e){let t="";function s(e){return encodeURIComponent(e).replace(/%20/g,"+")}for(var n of e.entries())typeof n[1]=="string"&&(t+=(t?"&":"")+s(n[0])+"="+s(n[1]));return t}function sendChunkComplete(e,t){var s=new XMLHttpRequest;s.open("POST","./uploadComplete",!0),s.setRequestHeader("Content-Type","application/x-www-form-urlencoded");let n=new FormData;n.append("allowedDownloads",document.getElementById("allowedDownloads").value),n.append("expiryDays",document.getElementById("expiryDays").value),n.append("password",document.getElementById("password").value),n.append("isUnlimitedDownload",!document.getElementById("enableDownloadLimit").checked),n.append("isUnlimitedTime",!document.getElementById("enableTimeLimit").checked),n.append("chunkid",e.upload.uuid),e.isEndToEndEncrypted===!0?(n.append("filesize",e.sizeEncrypted),n.append("filename","Encrypted File"),n.append("filecontenttype",""),n.append("isE2E","true"),n.append("realSize",e.size)):(n.append("filesize",e.size),n.append("filename",e.name),n.append("filecontenttype",e.type)),s.onreadystatechange=function(){if(this.readyState==4)if(this.status==200){let n=addRow(s.response);if(e.isEndToEndEncrypted===!0){try{let s=GokapiE2EAddFile(e.upload.uuid,n,e.name);if(s instanceof Error)throw s;let t=GokapiE2EInfoEncrypt();if(t instanceof Error)throw t;storeE2EInfo(t)}catch(t){e.accepted=!1,dropzoneObject._errorProcessing([e],t);return}GokapiE2EDecryptMenu()}removeFileStatus(e.upload.uuid),t()}else{e.accepted=!1;let t=getErrorMessage(s.responseText);dropzoneObject._errorProcessing([e],t),showError(e,t)}},s.send(urlencodeFormData(n))}function getErrorMessage(e){let t;try{t=JSON.parse(e)}catch{return"Unknown error: Server could not process file"}return"Error: "+t.ErrorMessage}function showError(e,t){let n=e.upload.uuid;document.getElementById(`us-progressbar-${n}`).style.width="100%",document.getElementById(`us-progressbar-${n}`).style.backgroundColor="red",document.getElementById(`us-progress-info-${n}`).innerText=t,document.getElementById(`us-progress-info-${n}`).classList.add("uploaderror")}function editFile(){const e=document.getElementById("mb_save");e.disabled=!0;let i="./api/files/modify",n=document.getElementById("mi_edit_down").value,s=document.getElementById("mi_edit_expiry").value,t=document.getElementById("mi_edit_pw").value,o=t==="(unchanged)";document.getElementById("mc_download").checked||(n=0),document.getElementById("mc_expiry").checked||(s=0),document.getElementById("mc_password").checked||(o=!1,t="");const a={method:"PUT",headers:{"Content-Type":"application/json",id:e.getAttribute("data-fileid"),allowedDownloads:n,expiryTimestamp:s,password:t,originalPassword:o}};fetch(i,a).then(e=>{if(!e.ok)throw new Error(`Request failed with status: ${e.status}`)}).then(e=>{location.reload()}).catch(t=>{alert("Unable to edit file: "+t),console.error("Error:",t),e.disabled=!1})}calendarInstance=null;function createCalendar(e){const t=new Date(e*1e3);calendarInstance=flatpickr("#mi_edit_expiry",{enableTime:!0,dateFormat:"U",altInput:!0,altFormat:"Y-m-d H:i",allowInput:!0,time_24hr:!0,defaultDate:t,minDate:"today"})}function handleEditCheckboxChange(e){var t=document.getElementById(e.getAttribute("data-toggle-target")),n=e.getAttribute("data-timestamp");e.checked?(t.classList.remove("disabled"),t.removeAttribute("disabled"),n!=null&&(calendarInstance._input.disabled=!1)):(n!=null&&(calendarInstance._input.disabled=!0),t.classList.add("disabled"),t.setAttribute("disabled",!0))}function showEditModal(e,t,n,s,o,i,a){document.getElementById("m_filenamelabel").innerHTML=e,document.getElementById("mc_expiry").setAttribute("data-timestamp",s),document.getElementById("mb_save").setAttribute("data-fileid",t),createCalendar(s),i?(document.getElementById("mi_edit_down").value="1",document.getElementById("mi_edit_down").disabled=!0,document.getElementById("mc_download").checked=!1):(document.getElementById("mi_edit_down").value=n,document.getElementById("mi_edit_down").disabled=!1,document.getElementById("mc_download").checked=!0),a?(document.getElementById("mi_edit_expiry").value=add14DaysIfBeforeCurrentTime(s),document.getElementById("mi_edit_expiry").disabled=!0,document.getElementById("mc_expiry").checked=!1,calendarInstance._input.disabled=!0):(document.getElementById("mi_edit_expiry").value=s,document.getElementById("mi_edit_expiry").disabled=!1,document.getElementById("mc_expiry").checked=!0,calendarInstance._input.disabled=!1),o?(document.getElementById("mi_edit_pw").value="(unchanged)",document.getElementById("mi_edit_pw").disabled=!1,document.getElementById("mc_password").checked=!0):(document.getElementById("mi_edit_pw").value="",document.getElementById("mi_edit_pw").disabled=!0,document.getElementById("mc_password").checked=!1),new bootstrap.Modal("#modaledit",{}).show()}function selectTextForPw(e){e.value==="(unchanged)"&&e.setSelectionRange(0,e.value.length)}function add14DaysIfBeforeCurrentTime(e){let t=Date.now(),n=e*1e3;if(n{if(!e.ok)throw new Error(`Request failed with status: ${e.status}`)}).then(e=>{o?s.classList.add("apiperm-notgranted"):s.classList.add("apiperm-granted"),s.classList.remove("apiperm-processing")}).catch(e=>{o?s.classList.add("apiperm-granted"):s.classList.add("apiperm-notgranted"),s.classList.remove("apiperm-processing"),alert("Unable to set permission: "+e),console.error("Error:",e)})}function checkBoxChanged(e,t){let n=!e.checked;n?document.getElementById(t).setAttribute("disabled",""):document.getElementById(t).removeAttribute("disabled"),t==="password"&&n&&(document.getElementById("password").value="")}function parseData(e){return e?typeof e=="object"?e:typeof e=="string"?JSON.parse(e):{Result:"error"}:{Result:"error"}}function registerChangeHandler(){const e=new EventSource("./uploadStatus");e.onmessage=e=>{try{let t=JSON.parse(e.data);setProgressStatus(t.chunkid,t.currentstatus)}catch(e){console.error("Failed to parse event data:",e)}},e.onerror=t=>{t.target.readyState!==EventSource.CLOSED&&e.close(),console.log("Reconnecting to SSE..."),setTimeout(registerChangeHandler,5e3)}}statusItemCount=0;function addFileStatus(e,t){const n=document.createElement("div");n.setAttribute("id",`us-container-${e}`),n.classList.add("us-container");const a=document.createElement("div");a.classList.add("filename"),a.textContent=t,n.appendChild(a);const s=document.createElement("div");s.classList.add("upload-progress-container"),s.setAttribute("id",`us-progress-container-${e}`);const r=document.createElement("div");r.classList.add("upload-progress-bar");const o=document.createElement("div");o.setAttribute("id",`us-progressbar-${e}`),o.classList.add("upload-progress-bar-progress"),o.style.width="0%",r.appendChild(o);const i=document.createElement("div");i.setAttribute("id",`us-progress-info-${e}`),i.classList.add("upload-progress-info"),i.textContent="0%",s.appendChild(r),s.appendChild(i),n.appendChild(s),n.setAttribute("data-starttime",Date.now()),n.setAttribute("data-complete","false");const c=document.getElementById("uploadstatus");c.appendChild(n),c.style.visibility="visible",statusItemCount++}function removeFileStatus(e){const t=document.getElementById(`us-container-${e}`);if(t==null)return;t.remove(),statusItemCount--,statusItemCount<1&&(document.getElementById("uploadstatus").style.visibility="hidden")}function addRow(e){let r=parseData(e);if(r.Result!=="OK"){alert("Failed to upload file!"),location.reload();return}let t=r.FileInfo,p=document.getElementById("downloadtable"),s=p.insertRow(0),i=s.insertCell(0),a=s.insertCell(1),o=s.insertCell(2),c=s.insertCell(3),l=s.insertCell(4),d=s.insertCell(5),u=s.insertCell(6),h="";t.IsPasswordProtected===!0&&(h=' '),i.innerText=t.Name,i.id="cell-name-"+t.Id,a.innerText=t.Size,t.UnlimitedDownloads?o.innerText="Unlimited":o.innerText=t.DownloadsRemaining,t.UnlimitedTime?c.innerText="Unlimited":c.innerText=t.ExpireAtString,l.innerHTML="0",d.innerHTML=''+t.Id+""+h;let n=' ';t.UrlHotlink===""?n=n+' ':n=n+' ',n=n+' `,n=n+` ',n=n+``,u.innerHTML=n,i.style.backgroundColor="green",a.style.backgroundColor="green",a.setAttribute("data-order",r.FileInfo.SizeBytes),o.style.backgroundColor="green",c.style.backgroundColor="green",l.style.backgroundColor="green",d.style.backgroundColor="green",u.style.backgroundColor="green";let m=$("#maintable").DataTable();rowCount==-1&&(rowCount=m.rows().count()),rowCount=rowCount+1,m.row.add(s);let f=document.getElementsByClassName("dataTables_empty")[0];return typeof f!="undefined"?f.innerText="Files stored: "+rowCount:document.getElementsByClassName("dataTables_info")[0].innerText="Files stored: "+rowCount,t.Id}function hideQrCode(){document.getElementById("qroverlay").style.display="none",document.getElementById("qrcode").innerHTML=""}function showQrCode(e){const t=document.getElementById("qroverlay");t.style.display="block",new QRCode(document.getElementById("qrcode"),{text:e,width:200,height:200,colorDark:"#000000",colorLight:"#ffffff",correctLevel:QRCode.CorrectLevel.H}),t.addEventListener("click",hideQrCode)}function showToast(){let e=document.getElementById("toastnotification");e.classList.add("show"),setTimeout(()=>{e.classList.remove("show")},1e3)} \ No newline at end of file diff --git a/makefile b/makefile index fc678f2..47c1591 100644 --- a/makefile +++ b/makefile @@ -9,17 +9,15 @@ all: build # Build Gokapi binary build: - @echo "Generating code..." - @echo - go generate ./... @echo "Building binary..." @echo + go generate ./... CGO_ENABLED=0 go build $(BUILD_FLAGS) -o $(OUTPUT_BIN) $(GOPACKAGE)/cmd/gokapi coverage: @echo Generating coverage @echo - go test ./... -parallel 8 --tags=test,awsmock -coverprofile=/tmp/coverage1.out && go tool cover -html=/tmp/coverage1.out + GOKAPI_AWS_BUCKET="gokapi" GOKAPI_AWS_REGION="eu-central-1" GOKAPI_AWS_KEY="keyid" GOKAPI_AWS_KEY_SECRET="secret" go test ./... -parallel 8 --tags=test,awstest -coverprofile=/tmp/coverage1.out && go tool cover -html=/tmp/coverage1.out coverage-specific: @echo Generating coverage for "$(TEST_PACKAGE)"