diff --git a/cmd/server/server.go b/cmd/server/server.go index 1ea4fac..1704db6 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -36,6 +36,7 @@ func main() { conf := config.Get() conf.Load(*ini) manager.Get().Db.AutoMigrate(&model.User{}) + manager.Get().Db.AutoMigrate(&model.BankAccount{}) e := echo.New() e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret")))) diff --git a/database/model/bank_account.go b/database/model/bank_account.go new file mode 100644 index 0000000..a2d1ed2 --- /dev/null +++ b/database/model/bank_account.go @@ -0,0 +1,20 @@ +package model + +import ( + "time" +) + +type BankAccount struct { + ID uint `gorm:"primaryKey" json:"id"` + Label string `gorm:"unique;not null" json:"label"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func NewBankAccount(label string) *BankAccount { + account := BankAccount{ + Label: label, + } + + return &account +} diff --git a/database/model/user.go b/database/model/user.go index 61e2512..f5e1c61 100644 --- a/database/model/user.go +++ b/database/model/user.go @@ -11,9 +11,9 @@ import ( type User struct { ID uint `gorm:"primaryKey" json:"id"` - Username string `gorm:"unique" json:"username"` - Password string `json:"-"` - DisplayName string `json:"display_name"` + Username string `gorm:"unique;not null" json:"username"` + Password string `gorm:"not null" json:"-"` + DisplayName string `gorm:"not null" json:"display_name"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } diff --git a/frontend/js/App.vue b/frontend/js/App.vue index 3c137df..00cfb92 100644 --- a/frontend/js/App.vue +++ b/frontend/js/App.vue @@ -17,6 +17,9 @@ import { BNavItem } from 'bootstrap-vue-next' {{ route.name }} + + {{ route.name }} + diff --git a/frontend/js/components/SortButton.vue b/frontend/js/components/SortButton.vue index 220bd80..11a032c 100644 --- a/frontend/js/components/SortButton.vue +++ b/frontend/js/components/SortButton.vue @@ -2,8 +2,10 @@ {{ props.label }} - - + + + + diff --git a/frontend/js/router/index.js b/frontend/js/router/index.js index 4188730..6b9c09b 100644 --- a/frontend/js/router/index.js +++ b/frontend/js/router/index.js @@ -13,6 +13,11 @@ const router = createRouter({ name: 'Utilisateurs', component: () => import('../views/UsersView.vue') }, + { + path: '/bank_accounts', + name: 'Comptes bancaires', + component: () => import('../views/BankAccountsView.vue') + }, ] }) diff --git a/frontend/js/views/BankAccountsView.vue b/frontend/js/views/BankAccountsView.vue new file mode 100644 index 0000000..0f79206 --- /dev/null +++ b/frontend/js/views/BankAccountsView.vue @@ -0,0 +1,226 @@ + + + diff --git a/frontend/js/views/TransactionsView.vue b/frontend/js/views/TransactionsView.vue index a443791..ced8e88 100644 --- a/frontend/js/views/TransactionsView.vue +++ b/frontend/js/views/TransactionsView.vue @@ -1,168 +1,5 @@ diff --git a/frontend/js/views/UsersView.vue b/frontend/js/views/UsersView.vue index 4171f66..a1740b7 100644 --- a/frontend/js/views/UsersView.vue +++ b/frontend/js/views/UsersView.vue @@ -1,5 +1,11 @@ @@ -46,7 +62,6 @@ import { BTr, BTd, BTh, - BRow, BContainer, BTableSimple, BModal, @@ -55,6 +70,7 @@ import { BFormGroup, BFormInput, BAlert, + BButtonToolbar, } from 'bootstrap-vue-next' import SortButton from './../components/SortButton.vue' @@ -63,15 +79,21 @@ import {ref, onMounted, reactive} from 'vue' const data = ref(null) const order = ref(null) const sort = ref(null) -const page = ref(null) +const page = ref(1) const pages = ref(null) const limit = ref(null) const form = ref(null) const formShow = ref(false) +const endpoint = `/api/user` -const refresh = (query) => { - fetch(`/api/user?${new URLSearchParams(query)}`) - .then(function(response) { +const refresh = () => { + fetch(`${endpoint}?${new URLSearchParams({ + order: order.value, + sort: sort.value, + page: page.value, + limit: limit.value, + })}`) + .then((response) => { return response.json() }) .then(function(value) { @@ -85,11 +107,13 @@ const refresh = (query) => { } const doEdit = (item) => { + const data = {...item} + form.value = { - action: `api/user/?${item.id}`, + action: `${endpoint}/${item.id}`, method: 'POST', - data: item, - label: item.display_name, + data: data, + label: data.display_name, error: null, fields: [ { @@ -116,8 +140,82 @@ const doEdit = (item) => { formShow.value = true } +const doAdd = () => { + const data = {display_name: null, username: null, password: null} + + form.value = { + action: `${endpoint}`, + method: 'POST', + data: data, + label: 'Nouveau', + error: null, + fields: [ + { + label: 'Nom', + type: 'text', + required: true, + key: 'display_name', + }, + { + label: 'Nom d\'utilisateur', + type: 'text', + required: true, + key: 'username', + }, + { + label: 'Mot de passe', + type: 'password', + required: false, + key: 'password', + }, + ] + } + + formShow.value = true +} + +const doDelete = (e) => { + fetch(`${endpoint}/${form.value.data.id}`, { + method: 'DELETE', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + }) + .then(() => { + formShow.value = false + refresh() + }) +} + const doSave = (e) => { - console.log(form.value) + e.preventDefault() + + const url = form.value.data.id ? `${endpoint}/${form.value.data.id}` : endpoint + + fetch(url, { + method: form.value.method, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(form.value.data), + }) + .then((response) => { + return response.json() + }) + .then((data) => { + if (data.code === 400) { + form.value.error = data.message + } else { + form.value = null + formShow.value = false + refresh() + } + }) + .catch((err) => { + form.value.error = `Une erreur s'est produite : ${err}` + }) } const doSort = (key) => { @@ -127,14 +225,11 @@ const doSort = (key) => { nextSort = (sort.value === 'asc' ? 'desc' : 'asc') } - refresh({ - order: key, - sort: nextSort, - limit: limit, - page: 1, - }) + order.value = key + sort.value = nextSort + page.value = 1 - sort.value = key + refresh() } const fields = [ diff --git a/web/controller/bank_account/controller.go b/web/controller/bank_account/controller.go new file mode 100644 index 0000000..c1a051a --- /dev/null +++ b/web/controller/bank_account/controller.go @@ -0,0 +1,102 @@ +package bank_account + +import ( + "github.com/labstack/echo/v4" + "gitnet.fr/deblan/budget/database/model" + "gitnet.fr/deblan/budget/web/controller/crud" + "gorm.io/gorm" +) + +type Controller struct { + crud *crud.Controller +} + +func (ctrl *Controller) Config() crud.Configuration { + return crud.Configuration{ + Model: model.BankAccount{}, + Models: []model.BankAccount{}, + ValidOrders: []string{"id", "display_name", "username"}, + ValidLimits: []int{20, 50, 100}, + DefaultLimit: 20, + CreateModel: func() interface{} { + return new(model.BankAccount) + }, + } +} + +func New(e *echo.Echo) *Controller { + c := Controller{ + crud: crud.New(), + } + + e.GET("/api/bank_account", c.List) + e.POST("/api/bank_account", c.Create) + e.GET("/api/bank_account/:id", c.Show) + e.POST("/api/bank_account/:id", c.Update) + e.DELETE("/api/bank_account/:id", c.Delete) + + return &c +} + +func (ctrl *Controller) List(c echo.Context) error { + if nil == model.LoadSessionUser(c) { + return c.Redirect(302, "/login") + } + + return ctrl.crud.With(ctrl.Config()).List(c) +} + +func (ctrl *Controller) Show(c echo.Context) error { + if nil == model.LoadSessionUser(c) { + return c.Redirect(302, "/login") + } + + return ctrl.crud.With(ctrl.Config()).Show(c) +} + +func (ctrl *Controller) Delete(c echo.Context) error { + if nil == model.LoadSessionUser(c) { + return c.Redirect(302, "/login") + } + + return ctrl.crud.With(ctrl.Config()).Delete(c) +} + +func (ctrl *Controller) Create(c echo.Context) error { + if nil == model.LoadSessionUser(c) { + return c.Redirect(302, "/login") + } + + type body struct { + Label string `json:"label" form:"label" validate:"required"` + } + + return ctrl.crud.With(ctrl.Config()).Create(c, new(body), func(db *gorm.DB, v interface{}) (interface{}, error) { + value := v.(*body) + item := model.NewBankAccount(value.Label) + + db.Model(ctrl.crud.Config.Model).Create(&item) + + return item, nil + }) +} + +func (ctrl *Controller) Update(c echo.Context) error { + if nil == model.LoadSessionUser(c) { + return c.Redirect(302, "/login") + } + + type body struct { + Label string `json:"label" form:"label" validate:"required"` + } + + return ctrl.crud.With(ctrl.Config()).Update(c, new(body), func(db *gorm.DB, a, b interface{}) (interface{}, error) { + item := a.(*model.BankAccount) + update := b.(*body) + item.Label = update.Label + + db.Model(ctrl.crud.Config.Model).Where("id = ?", item.ID).Save(&item) + + return item, nil + }) +} diff --git a/web/controller/crud/crud.go b/web/controller/crud/crud.go index 1653aa3..714e410 100644 --- a/web/controller/crud/crud.go +++ b/web/controller/crud/crud.go @@ -11,6 +11,7 @@ import ( ) type UpdateCallback func(*gorm.DB, interface{}, interface{}) (interface{}, error) +type CreateCallback func(*gorm.DB, interface{}) (interface{}, error) type Error struct { Code int `json:"code"` @@ -96,6 +97,59 @@ func (ctrl *Controller) Show(c echo.Context) error { return c.JSON(200, item) } +func (ctrl *Controller) Delete(c echo.Context) error { + db := manager.Get().Db + value, err := strconv.Atoi(c.Param("id")) + + if err != nil { + return err + } + + var count int64 + db.Model(ctrl.Config.Model).Where("id = ?", value).Count(&count) + + if count == 0 { + return c.JSON(404, Error{ + Code: 404, + Message: "Not found", + }) + } + + item := ctrl.Config.CreateModel() + db.Model(ctrl.Config.Model).Where("id = ?", value).Delete(&item) + + return c.JSON(200, nil) +} + +func (ctrl *Controller) Create(c echo.Context, body interface{}, createCallback CreateCallback) error { + db := manager.Get().Db + + if err := c.Bind(body); err != nil { + return c.JSON(400, Error{ + Code: 400, + Message: "Bad request", + }) + } + + if err := c.Validate(body); err != nil { + return c.JSON(400, Error{ + Code: 400, + Message: err.Error(), + }) + } + + result, err := createCallback(db, body) + + if err != nil { + return c.JSON(400, Error{ + Code: 400, + Message: err.Error(), + }) + } + + return c.JSON(200, result) +} + func (ctrl *Controller) Update(c echo.Context, body interface{}, updateCallback UpdateCallback) error { db := manager.Get().Db value, err := strconv.Atoi(c.Param("id")) diff --git a/web/controller/user/controller.go b/web/controller/user/controller.go index c21fe90..325e8dd 100644 --- a/web/controller/user/controller.go +++ b/web/controller/user/controller.go @@ -30,8 +30,10 @@ func New(e *echo.Echo) *Controller { } e.GET("/api/user", c.List) + e.POST("/api/user", c.Create) e.GET("/api/user/:id", c.Show) e.POST("/api/user/:id", c.Update) + e.DELETE("/api/user/:id", c.Delete) return &c } @@ -52,12 +54,42 @@ func (ctrl *Controller) Show(c echo.Context) error { return ctrl.crud.With(ctrl.Config()).Show(c) } +func (ctrl *Controller) Delete(c echo.Context) error { + if nil == model.LoadSessionUser(c) { + return c.Redirect(302, "/login") + } + + return ctrl.crud.With(ctrl.Config()).Delete(c) +} + +func (ctrl *Controller) Create(c echo.Context) error { + if nil == model.LoadSessionUser(c) { + return c.Redirect(302, "/login") + } + + type body struct { + Username string `json:"username" form:"username" validate:"required"` + DisplayName string `json:"display_name" form:"display_name" validate:"required"` + Password string `json:"password" form:"password" validate:"required"` + } + + return ctrl.crud.With(ctrl.Config()).Create(c, new(body), func(db *gorm.DB, v interface{}) (interface{}, error) { + value := v.(*body) + item := model.NewUser(value.Username, value.Password, value.DisplayName) + + db.Model(ctrl.crud.Config.Model).Save(&item) + + return item, nil + }) +} + func (ctrl *Controller) Update(c echo.Context) error { if nil == model.LoadSessionUser(c) { return c.Redirect(302, "/login") } type body struct { + Username string `json:"username" form:"username" validate:"required"` DisplayName string `json:"display_name" form:"display_name" validate:"required"` Password string `json:"password" form:"password"` } diff --git a/web/router/router.go b/web/router/router.go index b120cd7..9982cf8 100644 --- a/web/router/router.go +++ b/web/router/router.go @@ -4,6 +4,7 @@ import ( "github.com/labstack/echo/v4" "gitnet.fr/deblan/budget/web/controller/app" "gitnet.fr/deblan/budget/web/controller/auth" + "gitnet.fr/deblan/budget/web/controller/bank_account" "gitnet.fr/deblan/budget/web/controller/user" ) @@ -11,4 +12,5 @@ func RegisterControllers(e *echo.Echo) { auth.New(e) app.New(e) user.New(e) + bank_account.New(e) }