From 8ece8306dd5ba63fe1ac05fe0706e7a5bd8c432f Mon Sep 17 00:00:00 2001 From: Andre Henriques Date: Wed, 17 Apr 2024 14:56:57 +0100 Subject: [PATCH] add ability to remove user and add task depndencies closes #69 --- go.mod | 3 + go.sum | 13 ++ logic/db_types/types.go | 32 ++--- logic/db_types/user.go | 3 +- logic/models/data.go | 10 ++ logic/models/delete.go | 19 ++- logic/models/run.go | 2 +- logic/models/train/train.go | 8 +- logic/tasks/handleUpload.go | 2 + logic/tasks/runner/runner.go | 24 +++- logic/tasks/utils/utils.go | 36 +++++- users.go => logic/users/users.go | 112 +++++++++++++++++- logic/utils/config.go | 2 +- main.go | 3 +- sql/models.sql | 10 +- sql/tasks.sql | 14 ++- webpage/src/NavBar.svelte | 3 + webpage/src/lib/Notifications.svelte | 42 +++++++ webpage/src/lib/NotificationsStore.svelte.ts | 68 +++++++++++ webpage/src/lib/requests.svelte.ts | 6 +- webpage/src/routes/models/edit/+page.svelte | 18 ++- webpage/src/routes/user-deleted/+page.svelte | 8 ++ webpage/src/routes/user/info/+page.svelte | 3 +- .../src/routes/user/info/DeleteUser.svelte | 34 ++++++ webpage/src/styles/app.css | 18 +++ 25 files changed, 439 insertions(+), 54 deletions(-) rename users.go => logic/users/users.go (76%) create mode 100644 webpage/src/lib/Notifications.svelte create mode 100644 webpage/src/lib/NotificationsStore.svelte.ts create mode 100644 webpage/src/routes/user-deleted/+page.svelte create mode 100644 webpage/src/routes/user/info/DeleteUser.svelte diff --git a/go.mod b/go.mod index bd54c89..1b0929f 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,9 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.19.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index ff10af9..7d6727e 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,7 @@ github.com/charmbracelet/log v0.2.5 h1:1yVvyKCKVV639RR4LIq1iy1Cs1AKxuNO+Hx2LJtk7 github.com/charmbracelet/log v0.2.5/go.mod h1:nQGK8tvc4pS9cvVEH/pWJiZ50eUq1aoXUOjGpXvdD0k= github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw= github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e h1:9+2AEFZymTi25FIIcDwuzcOPH04z9+fV6XeLiGORPDI= @@ -34,6 +35,12 @@ github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -53,11 +60,15 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= @@ -87,3 +98,5 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logic/db_types/types.go b/logic/db_types/types.go index 29d60a8..7200369 100644 --- a/logic/db_types/types.go +++ b/logic/db_types/types.go @@ -68,35 +68,25 @@ type BaseModel struct { Status int Id string - ModelType int - ImageMode int - Width int - Height int - Format string + ModelType int `db:"model_type"` + ImageModeRaw string `db:"color_mode"` + ImageMode int `db:"0"` + Width int + Height int + Format string + CanTrain int `db:"can_train"` } var ModelNotFoundError = errors.New("Model not found error") func GetBaseModel(db *sql.DB, id string) (base *BaseModel, err error) { - rows, err := db.Query("select name, status, id, width, height, color_mode, format, model_type from models where id=$1;", id) + var model BaseModel + err = GetDBOnce(db, &model, "models where id=$1", id) if err != nil { return } - defer rows.Close() - - if !rows.Next() { - return nil, ModelNotFoundError - } - - base = &BaseModel{} - var colorMode string - err = rows.Scan(&base.Name, &base.Status, &base.Id, &base.Width, &base.Height, &colorMode, &base.Format, &base.ModelType) - if err != nil { - return nil, err - } - - base.ImageMode = StringToImageMode(colorMode) - return + model.ImageMode = StringToImageMode(model.ImageModeRaw) + return &model, nil } func (m BaseModel) CanEval() bool { diff --git a/logic/db_types/user.go b/logic/db_types/user.go index 735a443..ee91e64 100644 --- a/logic/db_types/user.go +++ b/logic/db_types/user.go @@ -7,7 +7,8 @@ import ( type UserType int const ( - User_Not_Auth UserType = iota + User_Deleted UserType = iota - 1 + User_Not_Auth User_Normal User_Admin ) diff --git a/logic/models/data.go b/logic/models/data.go index bdf5d77..47112a0 100644 --- a/logic/models/data.go +++ b/logic/models/data.go @@ -439,6 +439,8 @@ func handleDataUpload(handle *Handle) { return c.SendJSONStatus(http.StatusNotFound, "Model not found") } else if err != nil { return c.Error500(err) + } else if model.CanTrain == 0 { + return c.JsonBadRequest("Model can not be trained!") } // TODO mk this path configurable @@ -468,6 +470,10 @@ func handleDataUpload(handle *Handle) { model, err := GetBaseModel(c.Db, obj.Id) if err == ModelNotFoundError { return c.JsonBadRequest("Model not found") + } else if err != nil { + return c.E500M("Failed to get model information", err) + } else if model.CanTrain == 0 { + return c.JsonBadRequest("Model can not be trained!") } if model.ModelType != 2 && model.Status != CONFIRM_PRE_TRAINING || model.ModelType == 2 && model.Status != CONFIRM_PRE_TRAINING && model.Status != READY { @@ -516,6 +522,8 @@ func handleDataUpload(handle *Handle) { return c.JsonBadRequest("Could not find the model") } else if err != nil { return c.E500M("Error getting model information", err) + } else if model.CanTrain == 0 { + return c.JsonBadRequest("Model can not be trained!") } // TODO make this work for zip files as well @@ -622,6 +630,8 @@ func handleDataUpload(handle *Handle) { return c.SendJSONStatus(http.StatusNotFound, "Model not found") } else if err != nil { return c.Error500(err) + } else if model.CanTrain == 0 { + return c.JsonBadRequest("Model can not be trained!") } // TODO work in allowing the model to add new in the pre ready moment diff --git a/logic/models/delete.go b/logic/models/delete.go index e7fc286..ed0bf27 100644 --- a/logic/models/delete.go +++ b/logic/models/delete.go @@ -9,20 +9,27 @@ import ( . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" ) -func deleteModelJSON(c *Context, id string) *Error { - c.Logger.Warnf("Removing model with id: %s", id) - _, err := c.Db.Exec("delete from models where id=$1;", id) +func DeleteModel(c BasePack, id string) (err error) { + c.GetLogger().Warnf("Removing model with id: %s", id) + _, err = c.GetDb().Exec("delete from models where id=$1;", id) if err != nil { - return c.E500M("Failed to delete models", err) + return } model_path := path.Join("./savedData", id) - c.Logger.Warnf("Removing folder of model with id: %s at %s", id, model_path) + c.GetLogger().Warnf("Removing folder of model with id: %s at %s", id, model_path) err = os.RemoveAll(model_path) if err != nil { - return c.E500M("Failed to remove data", err) + return } + return +} +func deleteModelJSON(c *Context, id string) *Error { + err := DeleteModel(c, id) + if err != nil { + return c.E500M("Failed to delete models", err) + } return c.SendJSON(id) } diff --git a/logic/models/run.go b/logic/models/run.go index eda033c..8991cd9 100644 --- a/logic/models/run.go +++ b/logic/models/run.go @@ -120,7 +120,7 @@ func runModelExp(base BasePack, model *BaseModel, def_id string, inputImage *tf. func ClassifyTask(base BasePack, task Task) (err error) { task.UpdateStatusLog(base, TASK_RUNNING, "Runner running task") - model, err := GetBaseModel(base.GetDb(), task.ModelId) + model, err := GetBaseModel(base.GetDb(), *task.ModelId) if err != nil { task.UpdateStatusLog(base, TASK_FAILED_RUNNING, "Failed to obtain the model") return err diff --git a/logic/models/train/train.go b/logic/models/train/train.go index af51834..e695267 100644 --- a/logic/models/train/train.go +++ b/logic/models/train/train.go @@ -1625,7 +1625,7 @@ func trainExpandable(c *Context, model *BaseModel) { func RunTaskTrain(b BasePack, task Task) (err error) { l := b.GetLogger() - model, err := GetBaseModel(b.GetDb(), task.ModelId) + model, err := GetBaseModel(b.GetDb(), *task.ModelId) if err != nil { task.UpdateStatusLog(b, TASK_FAILED_RUNNING, "Failed to get model information") l.Error("Failed to get model information", "err", err) @@ -1683,7 +1683,7 @@ func RunTaskTrain(b BasePack, task Task) (err error) { } func RunTaskRetrain(b BasePack, task Task) (err error) { - model, err := GetBaseModel(b.GetDb(), task.ModelId) + model, err := GetBaseModel(b.GetDb(), *task.ModelId) if err != nil { return err } else if model.Status != READY_RETRAIN { @@ -1759,6 +1759,8 @@ func handleTrain(handle *Handle) { return c.JsonBadRequest("Model not found") } else if err != nil { return c.E500M("Failed to get model information", err) + } else if model.CanTrain == 0 { + return c.JsonBadRequest("Model can not be trained!") } if model.Status != CONFIRM_PRE_TRAINING { @@ -1813,6 +1815,8 @@ func handleTrain(handle *Handle) { return c.E500M("Faield to get model", err) } else if model.Status != READY && model.Status != READY_RETRAIN_FAILED && model.Status != READY_ALTERATION_FAILED { return c.JsonBadRequest("Model in invalid status for re-training") + } else if model.CanTrain == 0 { + return c.JsonBadRequest("Model can not be trained!") } c.Logger.Info("Expanding definitions for models", "id", model.Id) diff --git a/logic/tasks/handleUpload.go b/logic/tasks/handleUpload.go index 615a30a..962287d 100644 --- a/logic/tasks/handleUpload.go +++ b/logic/tasks/handleUpload.go @@ -55,6 +55,8 @@ func handleUpload(handler *Handle) { model, err := GetBaseModel(c.Db, requestData.ModelId) if err != nil { return c.Error500(err) + } else if model.CanTrain == 0 { + return c.JsonBadRequest("Model can not be trained!") } switch model.Status { diff --git a/logic/tasks/runner/runner.go b/logic/tasks/runner/runner.go index 6f5737a..8bae0da 100644 --- a/logic/tasks/runner/runner.go +++ b/logic/tasks/runner/runner.go @@ -14,6 +14,7 @@ import ( . "git.andr3h3nriqu3s.com/andr3/fyp/logic/models" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/train" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/tasks/utils" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/users" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" ) @@ -71,6 +72,14 @@ func runner(config Config, db *sql.DB, task_channel chan Task, index int, back_c logger.Error("Failed to tain the model", "error", err) } + back_channel <- index + continue + } else if task.TaskType == int(TASK_TYPE_DELETE_USER) { + logger.Warn("User deleting Task") + if err = DeleteUser(base, task); err != nil { + logger.Error("Failed to tain the model", "error", err) + } + back_channel <- index continue } @@ -168,16 +177,23 @@ func RunnerOrchestrator(db *sql.DB, config Config) { } if task_to_dispatch == nil { - var task Task - err := GetDBOnce(db, &task, "tasks where status=$1 limit 1", TASK_TODO) + var task TaskT + err := GetDBOnce(db, &task, "tasks as t "+ + // Get depenencies + "left join tasks_dependencies as td on t.id=td.main_id "+ + // Get the task that the depencey resolves to + "left join tasks as t2 on t2.id=td.dependent_id "+ + "where t.status=1 "+ + "group by t.id having count(td.id) filter (where t2.status in (0,1,2,3)) = 0;") if err != NotFoundError && err != nil { - log.Error("Failed to get tasks from db") + log.Error("Failed to get tasks from db", "err", err) continue } if err == NotFoundError { task_to_dispatch = nil } else { - task_to_dispatch = &task + temp := Task(task) + task_to_dispatch = &temp } } diff --git a/logic/tasks/utils/utils.go b/logic/tasks/utils/utils.go index 57caa2d..8a7f105 100644 --- a/logic/tasks/utils/utils.go +++ b/logic/tasks/utils/utils.go @@ -10,7 +10,7 @@ import ( type Task struct { Id string `db:"id" json:"id"` UserId string `db:"user_id" json:"user_id"` - ModelId string `db:"model_id" json:"model_id"` + ModelId *string `db:"model_id" json:"model_id"` Status int `db:"status" json:"status"` StatusMessage string `db:"status_message" json:"status_message"` UserConfirmed int `db:"user_confirmed" json:"user_confirmed"` @@ -21,6 +21,27 @@ type Task struct { CreatedOn time.Time `db:"created_on" json:"created"` } +// Find better way todo this +type TaskT struct { + Id string `db:"t.id" json:"id"` + UserId string `db:"t.user_id" json:"user_id"` + ModelId *string `db:"t.model_id" json:"model_id"` + Status int `db:"t.status" json:"status"` + StatusMessage string `db:"t.status_message" json:"status_message"` + UserConfirmed int `db:"t.user_confirmed" json:"user_confirmed"` + Compacted int `db:"t.compacted" json:"compacted"` + TaskType int `db:"t.task_type" json:"type"` + ExtraTaskInfo string `db:"t.extra_task_info" json:"extra_task_info"` + Result string `db:"t.result" json:"result"` + CreatedOn time.Time `db:"t.created_on" json:"created"` +} + +type TaskDependents struct { + Id string `db:"id" json:"id"` + MainId string `db:"main_id" json:"main_id"` + DependentId string `db:"dependent_id" json:"dependent_id"` +} + type TaskStatus int const ( @@ -39,6 +60,7 @@ const ( TASK_TYPE_CLASSIFICATION TaskType = 1 + iota TASK_TYPE_TRAINING TASK_TYPE_RETRAINING + TASK_TYPE_DELETE_USER ) type TaskAgreement int @@ -82,3 +104,15 @@ func (t Task) SetResult(base BasePack, result any) (err error) { _, err = base.GetDb().Exec("update tasks set result=$1 where id=$2", text, t.Id) return } + +func (t Task) Depend(base BasePack, depend_id string) (err error) { + var dependency = struct { + Main string `db:"main_id"` + Dependent string `db:"dependent_id"` + }{ + Main: t.Id, + Dependent: depend_id, + } + _, err = InsertReturnId(base.GetDb(), &dependency, "tasks_dependencies", "id") + return +} diff --git a/users.go b/logic/users/users.go similarity index 76% rename from users.go rename to logic/users/users.go index acdf69d..084842c 100644 --- a/users.go +++ b/logic/users/users.go @@ -1,4 +1,4 @@ -package main +package users import ( "crypto/rand" @@ -12,6 +12,8 @@ import ( . "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/models" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/tasks/utils" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" ) @@ -80,7 +82,31 @@ func generateToken(db *sql.DB, email string, password string, name string) (stri return token, true } -func usersEndpints(db *sql.DB, handle *Handle) { +func DeleteUser(base BasePack, task Task) (err error) { + ids, err := GetDbMultitple[JustId](base.GetDb(), "models where user_id=$1;", task.ExtraTaskInfo) + if err != nil { + task.UpdateStatusLog(base, TASK_FAILED_RUNNING, "Could not get models list") + return + } + + for i := range ids { + err = DeleteModel(base, ids[i].Id) + if err != nil { + base.GetLogger().Error("Could not delete model", "err", err) + } + } + + _, err = base.GetDb().Exec("delete from users where id=$1", task.ExtraTaskInfo) + if err != nil { + task.UpdateStatusLog(base, TASK_FAILED_RUNNING, "Could not delete user") + return + } + + task.UpdateStatusLog(base, TASK_DONE, "User deleted with success") + return +} + +func UsersEndpints(db *sql.DB, handle *Handle) { type UserLogin struct { Email string `json:"email"` @@ -98,6 +124,10 @@ func usersEndpints(db *sql.DB, handle *Handle) { return c.E500M("Failed to get user from token", err) } + if user.UserType == int(User_Deleted) { + return c.JsonBadRequest("Your user is in the process of being deleted!") + } + type UserReturn struct { Token string `json:"token"` Id string `json:"id"` @@ -381,4 +411,82 @@ func usersEndpints(db *sql.DB, handle *Handle) { return c.SendJSON("Ok") }) + + type DeleteUser struct { + Id string `json:"id" validate:"required"` + Password string `json:"password" validate:"required"` + } + DeleteAuthJson(handle, "/user/delete", User_Normal, func(c *Context, obj *DeleteUser) *Error { + // TODO let admin delete users + if c.User.Id != obj.Id { + return c.E500M("TODO implement this for admins", nil) + } + + // TODO let admin delete users + // Verify the user password + _, generated := generateToken(c.Db, c.User.Email, obj.Password, "User Verification") + if !generated { + return c.JsonBadRequest("Password provided is incorrect") + } + + // Disable the ability for the user to create new tasks + _, err := c.Db.Exec( + "update models as m set can_train=0 "+ + "from users as u "+ + "where u.id=$1 and u.id=m.user_id;", + obj.Id, + ) + if err != nil { + return c.E500M("Failed to stop the models", err) + } + + type CreateNewTask struct { + UserId string `db:"user_id"` + TaskType int `db:"task_type"` + Status int `db:"status"` + ExtraTaskInfo string `db:"extra_task_info"` + } + + newTask := CreateNewTask{ + UserId: c.Handle.Config.ServiceUser.UserId, + // TODO move this to an enum + TaskType: int(TASK_TYPE_DELETE_USER), + Status: TASK_PREPARING, + ExtraTaskInfo: obj.Id, + } + t_id, err := InsertReturnId(c, &newTask, "tasks", "id") + if err != nil { + return c.E500M("Failed to create task", err) + } + + task := Task{Id: t_id} + + type taskId struct { + Id string `db:"t.id"` + } + tasks, err := GetDbMultitple[taskId](c, "tasks as t "+ + "left join models as m on m.id=t.model_id "+ + "where (t.user_id=$1 or m.user_id=$1) and t.status in (0,1,2,3) "+ + "group by t.id;", + obj.Id) + + for i := range tasks { + err = task.Depend(c, tasks[i].Id) + if err != nil { + c.Logger.Error("Failed to mark task as depency", "err", err) + } + } + + err = task.UpdateStatus(c, TASK_TODO, "Task ready") + if err != nil { + return c.E500M("Failed to mark task as ready", err) + } + + _, err = c.Db.Exec("update users set user_type=-1 where id=$1", obj.Id) + if err != nil { + return c.E500M("Failed to delete user", err) + } + + return c.SendJSON("User to be deleted") + }) } diff --git a/logic/utils/config.go b/logic/utils/config.go index 4cbc7d5..4c9fbee 100644 --- a/logic/utils/config.go +++ b/logic/utils/config.go @@ -97,7 +97,7 @@ func (c *Config) Cleanup(db *sql.DB) { failLog(err) _, err = db.Exec("update models set status=$1 where status=$2", FAILED_PREPARING, PREPARING) failLog(err) - _, err = db.Exec("update tasks set status=$1 where status=$2", TASK_PICKED_UP, TASK_TODO) + _, err = db.Exec("update tasks set status=$1 where status=$2", TASK_TODO, TASK_PICKED_UP) failLog(err) tasks, err := GetDbMultitple[Task](db, "tasks where status=$1", TASK_RUNNING) diff --git a/main.go b/main.go index 047b436..cf53776 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( . "git.andr3h3nriqu3s.com/andr3/fyp/logic/models" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/tasks" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/tasks/runner" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/users" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" ) @@ -49,7 +50,7 @@ func main() { // TODO Handle this in other way handle.ReadTypesFilesApi("/savedData/", ".", []string{".png", ".jpeg"}, []string{"image/png", "image/jpeg"}) - usersEndpints(db, handle) + UsersEndpints(db, handle) HandleModels(handle) HandleTasks(handle) diff --git a/sql/models.sql b/sql/models.sql index 26d9ba4..130291e 100644 --- a/sql/models.sql +++ b/sql/models.sql @@ -10,13 +10,14 @@ create table if not exists models ( -- -1: failed preparing -- 1: preparing status integer default 1, - + -- Types: -- 0: Unset -- 1: simple -- 2: expandable model_type integer default 0, + can_train integer default 1, width integer, height integer, color_mode varchar (20), @@ -51,7 +52,7 @@ create table if not exists model_data_point ( status_message text ); --- drop table if exists model_definition; +-- drop table if exists model_definition; create table if not exists model_definition ( id uuid primary key default gen_random_uuid(), model_id uuid references models (id) on delete cascade, @@ -82,10 +83,10 @@ create table if not exists model_definition_layer ( -- ei 28,28,1 -- a 28x28 grayscale image shape text not null, - + -- Type based on the expandability -- 0: not expandalbe model - -- 1: fixed + -- 1: fixed -- 2: head exp_type integer default 0 ); @@ -111,4 +112,3 @@ create table if not exists exp_model_head ( created_on timestamp default current_timestamp, epoch_progress integer default 0 ); - diff --git a/sql/tasks.sql b/sql/tasks.sql index 0bcb6d3..8248ade 100644 --- a/sql/tasks.sql +++ b/sql/tasks.sql @@ -16,19 +16,25 @@ create table if not exists tasks ( result text default '', extra_task_info text default '', - + -- -1: user said task is wrong -- 0: no user input -- 1: user said task is ok user_confirmed integer default 0, - + -- Tells the user if the file has been already compacted into -- embendings compacted integer default 0, - + -- TODO move the training tasks to here -- 1: Classification task_type integer, created_on timestamp default current_timestamp -) +); + +create table if not exists tasks_dependencies ( + id uuid primary key default gen_random_uuid(), + main_id uuid references tasks (id) on delete cascade not null, + dependent_id uuid references tasks (id) on delete cascade not null +); diff --git a/webpage/src/NavBar.svelte b/webpage/src/NavBar.svelte index 6c87ba6..1bd3bae 100644 --- a/webpage/src/NavBar.svelte +++ b/webpage/src/NavBar.svelte @@ -1,4 +1,5 @@ @@ -26,6 +27,8 @@ + + diff --git a/webpage/src/lib/NotificationsStore.svelte.ts b/webpage/src/lib/NotificationsStore.svelte.ts new file mode 100644 index 0000000..d58f6bf --- /dev/null +++ b/webpage/src/lib/NotificationsStore.svelte.ts @@ -0,0 +1,68 @@ +type NotificationType = 'danger' | 'success' | 'info'; +type Notification = { + type?: NotificationType; + timeToLive?: number; + message: string; + html?: string; +}; + +export function createNotificationStore(defaultTimetoLive: number) { + type InternalNotifications = { + notification: Notification; + endDate: Date; + }; + let notifications = $state([]); + let timeout = $state(); + + function clearNotifications() { + const now = new Date().getTime(); + let min: number | undefined = undefined; + notifications = notifications.filter((a) => { + const t = a.endDate.getTime(); + if (t <= now) { + return false; + } + if (min == undefined) { + min = t; + } else if (min > t) { + min = t; + } + return true; + }); + if (min != undefined) { + timeout = setTimeout(clearNotifications, now - min); + } else { + timeout = undefined; + } + } + + function add(noti: Notification) { + if (!timeout) { + timeout = setTimeout(clearNotifications, noti.timeToLive ?? defaultTimetoLive); + } + const now = new Date(); + notifications.push({ + notification: noti, + endDate: new Date(now.getTime() + (noti.timeToLive ?? defaultTimetoLive)) + }); + } + + return { + get notifications() { + return notifications.map((a) => a.notification); + }, + clear() { + notifications.forEach((a) => clearInterval(a.timeout)); + notifications = []; + }, + add, + notify(message: string, type: NotificationType = 'danger') { + add({ message, type }); + }, + display(message: string) { + add({ message, type: 'danger' }); + } + }; +} + +export const notificationStore = createNotificationStore(5000); diff --git a/webpage/src/lib/requests.svelte.ts b/webpage/src/lib/requests.svelte.ts index afa9520..a1ce757 100644 --- a/webpage/src/lib/requests.svelte.ts +++ b/webpage/src/lib/requests.svelte.ts @@ -108,7 +108,11 @@ export async function showMessage( if (e == null) { return false; } else if (e instanceof Response) { - messages.display(await e.json()); + try { + messages.display(await e.json()); + } catch (ex) { + showMessage(ex, messages, message); + } return true; } else { console.error(e); diff --git a/webpage/src/routes/models/edit/+page.svelte b/webpage/src/routes/models/edit/+page.svelte index b1bae34..f3681f4 100644 --- a/webpage/src/routes/models/edit/+page.svelte +++ b/webpage/src/routes/models/edit/+page.svelte @@ -25,7 +25,7 @@ diff --git a/webpage/src/routes/user-deleted/+page.svelte b/webpage/src/routes/user-deleted/+page.svelte new file mode 100644 index 0000000..9070217 --- /dev/null +++ b/webpage/src/routes/user-deleted/+page.svelte @@ -0,0 +1,8 @@ +

Your User has been deleted

+ + diff --git a/webpage/src/routes/user/info/+page.svelte b/webpage/src/routes/user/info/+page.svelte index 6330695..280753b 100644 --- a/webpage/src/routes/user/info/+page.svelte +++ b/webpage/src/routes/user/info/+page.svelte @@ -7,6 +7,7 @@ import { post } from 'src/lib/requests.svelte'; import MessageSimple, { type DisplayFn } from 'src/lib/MessageSimple.svelte'; import TokenTable from './TokenTable.svelte'; + import DeleteUser from './DeleteUser.svelte'; onMount(() => { if (!userStore.isLogin()) { @@ -110,8 +111,8 @@ - + diff --git a/webpage/src/routes/user/info/DeleteUser.svelte b/webpage/src/routes/user/info/DeleteUser.svelte new file mode 100644 index 0000000..4cf9ade --- /dev/null +++ b/webpage/src/routes/user/info/DeleteUser.svelte @@ -0,0 +1,34 @@ + + +
+

Delete user

+ Deleting the user will delete all your data stored in the service including the images. +
+ To confirm please type your password + +
+ +
diff --git a/webpage/src/styles/app.css b/webpage/src/styles/app.css index 7b90b45..9550691 100644 --- a/webpage/src/styles/app.css +++ b/webpage/src/styles/app.css @@ -13,6 +13,7 @@ --warning: #fca311; --warning-transparent: #fca31144; + --danger-ligther: #ffe0de; --danger: #ff8282; --danger-transparent: #ff828244; @@ -171,3 +172,20 @@ a.button { background-color: var(--danger-transparent); border: 1px solid var(--danger); } + +.box { + padding: 30px; + margin: 20px 0; + border-radius: 10px; + box-shadow: 2px 5px 8px 2px #66666655; +} + +.no-top-margin { + margin-top: 0px; +} + +.box.danger-bg, +form.danger-bg { + background-color: var(--danger-transparent); + border: 1px solid var(--danger); +}