diff --git a/svc/api/acompany.go b/svc/api/acompany.go new file mode 100644 index 0000000..350edf4 --- /dev/null +++ b/svc/api/acompany.go @@ -0,0 +1,162 @@ +package api + +import ( + "bordro-esleme/model/company" + "git.makki.io/makki/libgo/cmn" + "git.makki.io/makki/libgo/dbu" + "git.makki.io/makki/libgo/mhttp" + "git.makki.io/makki/libgo/nauth" + "github.com/go-chi/chi/v5" + "net/http" +) + +func companyGet(w http.ResponseWriter, r *http.Request) { + /* + tc := auth.TokenDataFromRequest(r) + rbac, err := tc.RBAC(r.Context(), auth.MdCompany) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + if !rbac.IsGrantedOp(auth.OpRead) && !tc.IsAdmin() { + mhttp.Forbidden(w) + return + } + */ + + id := cmn.StrToInt64(chi.URLParam(r, "id")) + data, err := company.DbRead(r.Context(), id) + if err != nil { + if dbu.IsNoRowsErr(err) { + mhttp.NotFound(w) + return + } else { + mhttp.InternalServerError(w, err) + return + } + } + + mhttp.ResponseSuccess(w, data) +} + +func companyCreate(w http.ResponseWriter, r *http.Request) { + tc := nauth.TokenPayloadFromRequest(r) + /* + rbac, err := tc.RBAC(r.Context(), auth.MdCompany) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + if !rbac.IsGrantedOp(auth.OpCreate) && !tc.IsAdmin() { + mhttp.Forbidden(w) + return + } + */ + + data := company.New() + err := cmn.BodyToJsonReq(r, &data) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + + err = data.DbCreate(r.Context(), true, tc.UsrID()) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + + mhttp.ResponseSuccess(w, data.ID) +} + +func companyUpdate(w http.ResponseWriter, r *http.Request) { + tc := nauth.TokenPayloadFromRequest(r) + /* + rbac, err := tc.RBAC(r.Context(), auth.MdCompany) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + if !rbac.IsGrantedOp(auth.OpUpdate) && !tc.IsAdmin() { + mhttp.Forbidden(w) + return + } + */ + + data := company.New() + err := cmn.BodyToJsonReq(r, &data) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + + err = data.DbUpdate(r.Context(), true, tc.UsrID()) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + + mhttp.ResponseSuccess(w, true) +} + +func companyDelete(w http.ResponseWriter, r *http.Request) { + tc := nauth.TokenPayloadFromRequest(r) + /* + rbac, err := tc.RBAC(r.Context(), auth.MdCompany) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + if !rbac.IsGrantedOp(auth.OpDelete) && !tc.IsAdmin() { + mhttp.Forbidden(w) + return + } + */ + + id := cmn.StrToInt64(chi.URLParam(r, "id")) + + err := company.DbDelete(r.Context(), id, true, tc.UsrID()) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + + mhttp.ResponseSuccess(w, true) +} + +func companyList(w http.ResponseWriter, r *http.Request) { + /* + tc := auth.TokenDataFromRequest(r) + rbac, err := tc.RBAC(r.Context(), auth.MdCompany) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + if !rbac.IsGrantedOp(auth.OpRead) && !tc.IsAdmin() { + mhttp.Forbidden(w) + return + } + */ + + var data []company.Company + + tr, err := dbu.NewTableRequestFromRequest(r) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + + rp, err := dbu.NewRepoWithFile(r.Context(), "company", nil) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + + respData, err := rp.MList(tr, &data) + if err != nil { + mhttp.InternalServerError(w, err) + return + } + + mhttp.ResponseSuccess(w, respData) +} diff --git a/svc/api/public/login.go b/svc/api/public/login.go index f960e90..3129fd5 100644 --- a/svc/api/public/login.go +++ b/svc/api/public/login.go @@ -13,6 +13,6 @@ func login(w http.ResponseWriter, r *http.Request) { return } - clientResp := authResp.GetEndUserReponse() + clientResp := authResp.GetEndUserResponse() mhttp.ResponseSuccess(w, clientResp) } diff --git a/svc/api/zhandler.go b/svc/api/zhandler.go index 6bce11e..68aa576 100644 --- a/svc/api/zhandler.go +++ b/svc/api/zhandler.go @@ -62,7 +62,12 @@ func HttpHandler(re enums.TRunEnv) http.Handler { // Handle valid / invalid tokens. r.Use(nauth.CheckTokenValidity) - r.Get("/sy/companies", napi.CompanyList) + // company routes + r.Get("/company/{id:[0-9]+}", companyGet) + r.Get("/company", companyList) + r.Put("/company", companyUpdate) + r.Post("/company", companyCreate) + r.Delete("/company/{id:[0-9]+}", companyDelete) }) }) diff --git a/svc/go.mod b/svc/go.mod index 2954a7a..8cfd93f 100644 --- a/svc/go.mod +++ b/svc/go.mod @@ -1,6 +1,6 @@ module bordro-esleme -go 1.22.1 +go 1.22.2 toolchain go1.22.2 @@ -9,5 +9,6 @@ require ( git.notitek.com.tr/common/notgo v0.0.0-20240408194744-d12ce3096580 // indirect github.com/go-chi/chi/v5 v5.0.12 // indirect github.com/go-chi/cors v1.2.1 // indirect + github.com/guregu/null/v5 v5.0.0 // indirect github.com/segmentio/ksuid v1.0.4 // indirect ) diff --git a/svc/model/company/company.go b/svc/model/company/company.go new file mode 100644 index 0000000..acab88b --- /dev/null +++ b/svc/model/company/company.go @@ -0,0 +1,134 @@ +package company + +import ( + "context" + "git.makki.io/makki/libgo/dbu" + "github.com/guregu/null/v5" + "log/slog" + "reflect" +) + +type Company struct { + Clid int64 `db:"clid" json:"-"` + ID int64 `db:"id"` + Code string `db:"code"` + Title null.String `db:"title"` + IsActive bool `db:"is_active"` + Notes null.String `db:"notes"` + + Tmpl null.String `db:"tmpl"` +} + +func New() *Company { + m := &Company{ + IsActive: true, + } + + // m.__DetailStruct__ = dbu.NewDetailData[*__DetailStruct__]("__sql_filename__", "__sql_masterlink_field__", "__struct_master_field_link__", true) + + return m +} + +func DbRead(ctx context.Context, id int64) (*Company, error) { + rp, err := dbu.NewRepoWithFile(ctx, "company", nil) + if err != nil { + return nil, err + } + data := New() + //rp.AddDetail(data.Auths) + err = rp.Read(id, data) + return data, err +} + +func (m *Company) GetIDVal() int64 { + return m.ID +} + +func (m *Company) SetMasterLinkVal(masterField string, val int64) { + reflect.ValueOf(m).Elem().FieldByName(masterField).SetInt(val) +} + +func DbDelete(ctx context.Context, id int64, log bool, usrID *int64) error { + tx, err := dbu.DB.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) + + rp, err := dbu.NewRepoWithFile(ctx, "company", tx) + if err != nil { + return err + } + + if log { + var oldData *Company + oldData, err = DbRead(ctx, id) + if err != nil { + return dbu.ParsedErrSuppressNoRows(err) + } + err = dbu.DB.LogDMLTx("d", "company", oldData.ID, usrID, "", oldData, nil, tx) + if err != nil { + slog.Error(err.Error()) + } + } + + err = rp.Delete(id) + if err != nil { + return err + } + + return tx.Commit(ctx) +} + +func (m *Company) DbCreate(ctx context.Context, log bool, usrID *int64) error { + rp, err := dbu.NewRepoWithFile(ctx, "company", nil) + if err != nil { + return err + } + + err = rp.Create(m) + if err != nil { + return err + } + + if log { + err = dbu.DB.LogDMLTx("c", "company", m.ID, usrID, "", nil, m, nil) + if err != nil { + slog.Error(err.Error()) + } + } + + return nil +} + +func (m *Company) DbUpdate(ctx context.Context, log bool, usrID *int64) error { + tx, err := dbu.DB.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) + + rp, err := dbu.NewRepoWithFile(ctx, "company", tx) + if err != nil { + return err + } + + if log { + var oldData *Company + oldData, err = DbRead(ctx, m.ID) + if err != nil { + return err + } + err = dbu.DB.LogDMLTx("u", "company", oldData.ID, usrID, "", oldData, m, tx) + if err != nil { + slog.Error(err.Error()) + } + } + + err = rp.Update(m.ID, m) + if err != nil { + return err + } + + return tx.Commit(ctx) +} diff --git a/ui/package.json b/ui/package.json index f3b1094..0017fd8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -13,12 +13,14 @@ "build": "quasar build" }, "dependencies": { + "@intlify/vite-plugin-vue-i18n": "^7.0.0", "@quasar/extras": "^1.16.4", "axios": "^1.2.1", "jwt-decode": "^4.0.0", "pinia": "^2.0.11", "quasar": "^2.8.0", "vue": "^3.4.18", + "vue-i18n": "^9.9.0", "vue-router": "^4.0.12" }, "devDependencies": { diff --git a/ui/quasar.config.js b/ui/quasar.config.js index fc3579a..caf39fb 100644 --- a/ui/quasar.config.js +++ b/ui/quasar.config.js @@ -8,9 +8,8 @@ // Configuration for your app // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js - -const { configure } = require('quasar/wrappers'); - +const { configure } = require('quasar/wrappers') +const path = require('path') module.exports = configure(function (ctx) { return { @@ -23,12 +22,14 @@ module.exports = configure(function (ctx) { boot: [ 'axios', 'version', + 'i18n', 'pinia', + 'bus', ], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css css: [ - 'app.scss' + 'app.scss', ], // https://github.com/quasarframework/quasar/tree/dev/extras @@ -48,8 +49,8 @@ module.exports = configure(function (ctx) { // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build build: { target: { - browser: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ], - node: 'node20' + browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'], + node: 'node20', }, vueRouterMode: 'history', // available values: 'hash', 'history' @@ -98,19 +99,34 @@ module.exports = configure(function (ctx) { // viteVuePluginOptions: {}, vitePlugins: [ - ['vite-plugin-checker', { + [ + 'vite-plugin-checker', { eslint: { - lintCommand: 'eslint "./**/*.{js,mjs,cjs,vue}"' - } - }, { server: false }] - ] + lintCommand: 'eslint "./**/*.{js,mjs,cjs,vue}"', + }, + }, { server: false }, + ], + [ + '@intlify/vite-plugin-vue-i18n', { + // if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false` + // compositionOnly: false, + + // if you want to use named tokens in your Vue I18n messages, such as 'Hello {name}', + // you need to set `runtimeOnly: false` + // runtimeOnly: false, + + // you need to set i18n resource including paths ! + include: path.resolve(__dirname, './src/i18n/**'), + } + ], + ], }, // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer devServer: { // https: true - port:9400, - open: true // opens browser window automatically + port: 9403, + open: true, // opens browser window automatically }, // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework @@ -132,8 +148,8 @@ module.exports = configure(function (ctx) { 'Notify', 'Loading', 'Dialog', - 'Meta' - ] + 'Meta', + ], }, // animations: 'all', // --- includes all animations @@ -155,7 +171,7 @@ module.exports = configure(function (ctx) { // https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr ssr: { // ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name! - // will mess up SSR + // will mess up SSR // extendSSRWebserverConf (esbuildConf) {}, // extendPackageJson (json) {}, @@ -169,8 +185,8 @@ module.exports = configure(function (ctx) { // (gets superseded if process.env.PORT is specified at runtime) middlewares: [ - 'render' // keep this as last one - ] + 'render', // keep this as last one + ], }, // https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa @@ -194,7 +210,7 @@ module.exports = configure(function (ctx) { // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor capacitor: { - hideSplashscreen: true + hideSplashscreen: true, }, // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron @@ -223,18 +239,18 @@ module.exports = configure(function (ctx) { builder: { // https://www.electron.build/configuration/configuration - appId: 'tr.com.notitek.bres' - } + appId: 'tr.com.notitek.bres', + }, }, // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex bex: { contentScripts: [ - 'my-content-script' + 'my-content-script', ], // extendBexScriptsConf (esbuildConf) {} // extendBexManifestJson (json) {} - } + }, } -}); +}) diff --git a/ui/src/boot/bus.js b/ui/src/boot/bus.js new file mode 100644 index 0000000..253f14a --- /dev/null +++ b/ui/src/boot/bus.js @@ -0,0 +1,14 @@ +// a Quasar CLI boot file (let's say /src/boot/bus.js) + +import { EventBus } from 'quasar' +import { boot } from 'quasar/wrappers' + +export default boot(({ app }) => { + const bus = new EventBus() + + // for Options API + app.config.globalProperties.$bus = bus + + // for Composition API + app.provide('bus', bus) +}) diff --git a/ui/src/boot/i18n.js b/ui/src/boot/i18n.js new file mode 100644 index 0000000..c260391 --- /dev/null +++ b/ui/src/boot/i18n.js @@ -0,0 +1,16 @@ +import { boot } from 'quasar/wrappers' +import { createI18n } from 'vue-i18n' +import messages from 'src/i18n' + +const i18n = createI18n({ + locale: 'tr', + globalInjection: true, + messages +}) + +export default boot(({ app }) => { + // Set i18n instance on app + app.use(i18n) +}) + +export { i18n } diff --git a/ui/src/i18n/en.js b/ui/src/i18n/en.js new file mode 100644 index 0000000..3a6e443 --- /dev/null +++ b/ui/src/i18n/en.js @@ -0,0 +1,9 @@ +import base from "src/libjs/i18n/en" + +const prjI18n = { + menu: { + app: 'Applications' + }, +} + +export default {...base, ...prjI18n} diff --git a/ui/src/i18n/index.js b/ui/src/i18n/index.js new file mode 100644 index 0000000..7a4c0c9 --- /dev/null +++ b/ui/src/i18n/index.js @@ -0,0 +1,8 @@ +import enUS from './en' +import trTR from './tr' + +export default { + 'tr': trTR, + 'en-US': enUS, + 'en-GB': enUS, +} diff --git a/ui/src/i18n/tr.js b/ui/src/i18n/tr.js new file mode 100644 index 0000000..27a00e6 --- /dev/null +++ b/ui/src/i18n/tr.js @@ -0,0 +1,10 @@ +import base from "src/libjs/i18n/tr" + +const prjI18n = { + menu: { + app: 'Uygulamalar' + }, + Usr: {}, +} + +export default {...base, ...prjI18n} diff --git a/ui/src/layouts/panelLayout.vue b/ui/src/layouts/panelLayout.vue index 3a51ad2..8717514 100644 --- a/ui/src/layouts/panelLayout.vue +++ b/ui/src/layouts/panelLayout.vue @@ -1,26 +1,53 @@ diff --git a/ui/src/pages/companyList.vue b/ui/src/pages/companyList.vue new file mode 100644 index 0000000..d456bba --- /dev/null +++ b/ui/src/pages/companyList.vue @@ -0,0 +1,30 @@ + + + diff --git a/ui/src/pages/index.vue b/ui/src/pages/index.vue index a595c2d..69625e5 100644 --- a/ui/src/pages/index.vue +++ b/ui/src/pages/index.vue @@ -1,14 +1,15 @@