add ability to remove user and add task depndencies closes #69

This commit is contained in:
Andre Henriques 2024-04-17 14:56:57 +01:00
parent 00ddb91a22
commit 8ece8306dd
25 changed files with 439 additions and 54 deletions

3
go.mod
View File

@ -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

13
go.sum
View File

@ -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=

View File

@ -68,35 +68,25 @@ type BaseModel struct {
Status int
Id string
ModelType int
ImageMode int
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 {

View File

@ -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
)

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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")
})
}

View File

@ -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)

View File

@ -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)

View File

@ -17,6 +17,7 @@ create table if not exists models (
-- 2: expandable
model_type integer default 0,
can_train integer default 1,
width integer,
height integer,
color_mode varchar (20),
@ -111,4 +112,3 @@ create table if not exists exp_model_head (
created_on timestamp default current_timestamp,
epoch_progress integer default 0
);

View File

@ -31,4 +31,10 @@ create table if not exists tasks (
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
);

View File

@ -1,4 +1,5 @@
<script lang="ts">
import Notifications from './lib/Notifications.svelte';
import { userStore } from './routes/UserStore.svelte';
</script>
@ -26,6 +27,8 @@
</ul>
</nav>
<Notifications />
<style class="scss">
nav {
background: #ececec;

View File

@ -0,0 +1,42 @@
<script lang="ts">
import { fly, fade } from 'svelte/transition';
import { notificationStore } from './NotificationsStore.svelte';
</script>
<div class="notifications">
{#each notificationStore.notifications as noti}
<div
class="notification"
class:noti-success={noti.type === 'success'}
class:noti-danger={noti.type === 'danger'}
class:noti-info={noti.type === 'info'}
in:fly|global={{ duration: 300, y: -120, opacity: 0 }}
out:fly|global={{ duration: 300, y: -120, opacity: 1 }}
>
{noti.message}
{@html noti.html ?? ''}
</div>
{/each}
</div>
<style lang="scss">
.notifications {
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
width: max(300px, 30%);
.notification {
border: 1px solid black;
padding: 20px;
background: #ffffff;
margin: 15px 0;
border-radius: 20px;
&.noti-danger {
background: var(--danger-ligther);
}
}
}
</style>

View File

@ -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<InternalNotifications[]>([]);
let timeout = $state<number | undefined>();
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);

View File

@ -108,7 +108,11 @@ export async function showMessage(
if (e == null) {
return false;
} else if (e instanceof Response) {
try {
messages.display(await e.json());
} catch (ex) {
showMessage(ex, messages, message);
}
return true;
} else {
console.error(e);

View File

@ -25,7 +25,7 @@
</script>
<script lang="ts">
import { onMount } from 'svelte';
import { onDestroy, onMount } from 'svelte';
import BaseModelInfo from './BaseModelInfo.svelte';
import DeleteModel from './DeleteModel.svelte';
@ -50,16 +50,22 @@
let id: string | undefined = $state();
let re_query_timeout: number | undefined = undefined;
async function getModel() {
if (re_query_timeout) {
clearTimeout(re_query_timeout);
re_query_timeout = undefined;
}
try {
let temp_model: Model = await get(`models/edit?id=${id}`);
if ([3, 7, 6].includes(temp_model.status)) {
setTimeout(getModel, 2000);
re_query_timeout = setTimeout(getModel, 2000);
}
if (temp_model.status == 4) {
setTimeout(getModel, 5000);
re_query_timeout = setTimeout(getModel, 5000);
definitions = await get(`models/edit/definitions?id=${id}`);
}
@ -108,6 +114,12 @@
}
}
onDestroy(() => {
if (re_query_timeout) {
clearTimeout(re_query_timeout);
}
});
// Auto reload after 2s when model.status 3,4
</script>

View File

@ -0,0 +1,8 @@
<h1>Your User has been deleted</h1>
<style>
h1 {
text-align: center;
font-size: 40px;
}
</style>

View File

@ -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 @@
<button> Update </button>
</div>
</form>
<!-- TODO Delete -->
<TokenTable />
<DeleteUser />
</div>
</div>

View File

@ -0,0 +1,34 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { notificationStore } from 'src/lib/NotificationsStore.svelte';
import { rdelete, showMessage } from 'src/lib/requests.svelte';
import { userStore } from 'src/routes/UserStore.svelte';
let data = $state({ password: '' });
async function deleteUser() {
if (!userStore.user) {
return;
}
try {
await rdelete('user/delete', {
id: userStore.user?.id,
password: data.password
});
userStore.user = undefined;
goto('/user-deleted');
} catch (e) {
showMessage(e, notificationStore);
}
}
</script>
<form class="danger-bg" on:submit|preventDefault={deleteUser}>
<h2 class="no-top-margin">Delete user</h2>
Deleting the user will delete all your data stored in the service including the images.
<fieldset>
To confirm please type your password
<input name="password" type="password" required bind:value={data.password} />
</fieldset>
<button> Delete </button>
</form>

View File

@ -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);
}