init
This commit is contained in:
parent
eaa1ecd55e
commit
c24e4ae5a8
38 changed files with 8612 additions and 101 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1 +1,6 @@
|
|||
/_data
|
||||
/budget-go
|
||||
/foo
|
||||
/server
|
||||
/node_modules
|
||||
/web/view/static
|
||||
|
|
|
|||
9
bin/watch.sh
Executable file
9
bin/watch.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
while true; do
|
||||
./node_modules/.bin/webpack
|
||||
templ generate
|
||||
screen -S budget -d -m go run ./cmd/server
|
||||
inotifywait -r . -e close_write
|
||||
screen -X -S budget quit
|
||||
done
|
||||
|
|
@ -11,12 +11,13 @@ import (
|
|||
userDelete "gitnet.fr/deblan/budget/cli/user/delete"
|
||||
"gitnet.fr/deblan/budget/config"
|
||||
"gitnet.fr/deblan/budget/database/manager"
|
||||
"gitnet.fr/deblan/budget/database/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ini := flag.String("c", "config.ini", "Path to config.ini")
|
||||
config.Get().Load(*ini)
|
||||
manager.Get().AutoMigrate()
|
||||
manager.Get().Db.AutoMigrate(&model.User{})
|
||||
|
||||
app := &cli.App{
|
||||
Commands: []*cli.Command{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
"net/http"
|
||||
"text/template"
|
||||
|
||||
rice "github.com/GeertJohan/go.rice"
|
||||
"github.com/go-playground/validator"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo-contrib/session"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
|
@ -21,6 +23,14 @@ type TemplateRenderer struct {
|
|||
templates *template.Template
|
||||
}
|
||||
|
||||
type AppValidator struct {
|
||||
validator *validator.Validate
|
||||
}
|
||||
|
||||
func (cv *AppValidator) Validate(i interface{}) error {
|
||||
return cv.validator.Struct(i)
|
||||
}
|
||||
|
||||
func main() {
|
||||
ini := flag.String("c", "config.ini", "Path to config.ini")
|
||||
conf := config.Get()
|
||||
|
|
@ -29,6 +39,12 @@ func main() {
|
|||
|
||||
e := echo.New()
|
||||
e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret"))))
|
||||
e.Validator = &AppValidator{validator: validator.New()}
|
||||
e.Static("/static", "static")
|
||||
|
||||
assetHandler := http.FileServer(rice.MustFindBox("../../web/view/static").HTTPBox())
|
||||
e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler)))
|
||||
|
||||
router.RegisterControllers(e)
|
||||
|
||||
if err := e.Start(fmt.Sprintf("%s:%d", conf.Server.Address, conf.Server.Port)); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
[server]
|
||||
port = 1324
|
||||
address = "127.0.0.1"
|
||||
address = "0.0.0.0"
|
||||
|
||||
[security]
|
||||
secret = "e93865c991358ff7a14f9781fa33ba4f28c33bb8d1cf3490ce6fd68521513536"
|
||||
|
||||
[log]
|
||||
level = "debug"
|
||||
;level = "warn"
|
||||
|
||||
[database]
|
||||
dsn = "root:root@tcp(127.0.0.1:3306)/budget"
|
||||
dsn = "root:root@tcp(127.0.0.1:3306)/budget?parseTime=true"
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ import (
|
|||
)
|
||||
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
Username string `gorm:"unique"`
|
||||
Password string
|
||||
DisplayName string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"unique" json:"username"`
|
||||
Password string `json:"-"`
|
||||
DisplayName string `json:"display_name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func NewUser(username, password, displayName string) *User {
|
||||
|
|
@ -30,7 +30,7 @@ func NewUser(username, password, displayName string) *User {
|
|||
}
|
||||
|
||||
func (u *User) UpdatePassword(password string) {
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), 1000)
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
|
||||
u.Password = string(hashedPassword)
|
||||
}
|
||||
|
|
|
|||
28
frontend/js/App.vue
Normal file
28
frontend/js/App.vue
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<script setup>
|
||||
import { RouterLink, RouterView } from 'vue-router'
|
||||
import { BNavItem } from 'bootstrap-vue-next'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="d-flex flex-nowrap">
|
||||
<div class="d-flex flex-column flex-shrink-0 p-3 text-bg-dark" id="nav">
|
||||
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-white text-decoration-none">
|
||||
<span class="fs-4"><i class="fa-solid fa-coins"></i> Budget</span>
|
||||
</a>
|
||||
<hr>
|
||||
<ul class="nav nav-pills flex-column mb-auto">
|
||||
<RouterLink to="/" custom v-slot="{ href, route, navigate, isActive, isExactActive }">
|
||||
<BNavItem :href="href" :active="isActive">{{ route.name }}</BNavItem>
|
||||
</RouterLink>
|
||||
<RouterLink to="/users" custom v-slot="{ href, route, navigate, isActive, isExactActive }">
|
||||
<BNavItem :href="href" :active="isActive">{{ route.name }}</BNavItem>
|
||||
</RouterLink>
|
||||
</ul>
|
||||
</div>
|
||||
<RouterView id="body" />
|
||||
</main>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
24
frontend/js/components/SortButton.vue
Normal file
24
frontend/js/components/SortButton.vue
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<span>
|
||||
{{ props.label }}
|
||||
|
||||
<i v-if="isAsc()" class="ms-1 fa-solid fa-sort-up"></i>
|
||||
<i v-if="isDesc()" class="ms-1 fa-solid fa-sort-down"></i>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps(['currentOrder', 'currentSort', 'order', 'label'])
|
||||
|
||||
const isActive = () => {
|
||||
return props.currentOrder === props.order
|
||||
}
|
||||
|
||||
const isAsc = () => {
|
||||
return isActive() && props.currentSort === 'asc'
|
||||
}
|
||||
|
||||
const isDesc = () => {
|
||||
return isActive() && props.currentSort === 'desc'
|
||||
}
|
||||
</script>
|
||||
17
frontend/js/main.js
Normal file
17
frontend/js/main.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import '../scss/main.scss'
|
||||
import {
|
||||
createApp
|
||||
} from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import {
|
||||
createBootstrap
|
||||
} from 'bootstrap-vue-next'
|
||||
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createBootstrap())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
19
frontend/js/router/index.js
Normal file
19
frontend/js/router/index.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Transactions',
|
||||
component: () => import('../views/TransactionsView.vue')
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
name: 'Utilisateurs',
|
||||
component: () => import('../views/UsersView.vue')
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
||||
168
frontend/js/views/TransactionsView.vue
Normal file
168
frontend/js/views/TransactionsView.vue
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<template>
|
||||
<BContainer fluid>
|
||||
<BTable
|
||||
v-model:sort-by="sortBy"
|
||||
:sort-internal="true"
|
||||
:items="itemsTyped"
|
||||
:fields="fieldsTyped"
|
||||
:current-page="currentPage"
|
||||
:per-page="perPage"
|
||||
:filter="filter"
|
||||
:responsive="false"
|
||||
:filterable="filterOn"
|
||||
:small="true"
|
||||
:multisort="true"
|
||||
@filtered="onFiltered"
|
||||
>
|
||||
<template #cell(name)="row">
|
||||
{{ (row.value).first }}
|
||||
{{ (row.value).last }}
|
||||
</template>
|
||||
<template #cell(actions)="row">
|
||||
<BButton size="sm" class="mr-1" @click="info(row.item, row.index)"> Info modal </BButton>
|
||||
<BButton size="sm" @click="row.toggleDetails">
|
||||
{{ row.detailsShowing ? 'Hide' : 'Show' }} Details
|
||||
</BButton>
|
||||
</template>
|
||||
<template #row-details="row">
|
||||
<BCard>
|
||||
<ul>
|
||||
<li v-for="(value, key) in row.item" :key="key">{{ key }}: {{ value }}</li>
|
||||
<BButton size="sm" @click="row.toggleDetails"> Toggle Details </BButton>
|
||||
</ul>
|
||||
</BCard>
|
||||
</template>
|
||||
</BTable>
|
||||
<BModal
|
||||
:id="infoModal.id"
|
||||
v-model="infoModal.open"
|
||||
:title="infoModal.title"
|
||||
:ok-only="true"
|
||||
@hide="resetInfoModal"
|
||||
>
|
||||
<pre>{{ infoModal.content }}</pre>
|
||||
</BModal>
|
||||
</BContainer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
BButton,
|
||||
BFormSelect,
|
||||
BInputGroup,
|
||||
BFormCheckbox,
|
||||
BFormGroup,
|
||||
BCol,
|
||||
BFormInput,
|
||||
BInputGroupText,
|
||||
BFormCHeckbox,
|
||||
BPagination,
|
||||
BRow,
|
||||
BModal,
|
||||
BContainer,
|
||||
BTable,
|
||||
BTableSortBy
|
||||
} from 'bootstrap-vue-next'
|
||||
import {computed, reactive, ref} from 'vue'
|
||||
|
||||
const itemsTyped = [
|
||||
{isActive: true, age: 40, name: {first: 'Dickerson', last: 'Macdonald'}},
|
||||
{isActive: false, age: 21, name: {first: 'Larsen', last: 'Shaw'}},
|
||||
{
|
||||
isActive: false,
|
||||
age: 9,
|
||||
name: {first: 'Mini', last: 'Navarro'},
|
||||
_rowVariant: 'success',
|
||||
},
|
||||
{isActive: false, age: 89, name: {first: 'Geneva', last: 'Wilson'}},
|
||||
{isActive: true, age: 38, name: {first: 'Jami', last: 'Carney'}},
|
||||
{isActive: false, age: 27, name: {first: 'Essie', last: 'Dunlap'}},
|
||||
{isActive: true, age: 40, name: {first: 'Thor', last: 'Macdonald'}},
|
||||
{
|
||||
isActive: true,
|
||||
age: 87,
|
||||
name: {first: 'Larsen', last: 'Shaw'},
|
||||
_cellVariants: {age: 'danger', isActive: 'warning'},
|
||||
},
|
||||
{isActive: false, age: 26, name: {first: 'Mitzi', last: 'Navarro'}},
|
||||
{isActive: false, age: 22, name: {first: 'Genevieve', last: 'Wilson'}},
|
||||
{isActive: true, age: 38, name: {first: 'John', last: 'Carney'}},
|
||||
{isActive: false, age: 29, name: {first: 'Dick', last: 'Dunlap'}},
|
||||
]
|
||||
|
||||
const fieldsTyped = [
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Person full name',
|
||||
sortable: true,
|
||||
sortDirection: 'desc',
|
||||
},
|
||||
{
|
||||
key: 'sortableName',
|
||||
label: 'Person sortable name',
|
||||
sortable: true,
|
||||
sortDirection: 'desc',
|
||||
formatter: (_value, _key, item) =>
|
||||
item ? `${item.name.last}, ${item.name.first}` : 'Something went wrong',
|
||||
sortByFormatted: true,
|
||||
filterByFormatted: true,
|
||||
},
|
||||
{key: 'age', label: 'Person age', sortable: true, class: 'text-center'},
|
||||
{
|
||||
key: 'isActive',
|
||||
label: 'Is Active',
|
||||
formatter: (value) => (value ? 'Yes' : 'No'),
|
||||
sortable: true,
|
||||
sortByFormatted: true,
|
||||
filterByFormatted: true,
|
||||
},
|
||||
{key: 'actions', label: 'Actions'},
|
||||
]
|
||||
|
||||
const pageOptions = [
|
||||
{value: 5, text: '5'},
|
||||
{value: 10, text: '10'},
|
||||
{value: 15, text: '15'},
|
||||
{value: 100, text: 'Show a lot'},
|
||||
]
|
||||
|
||||
const totalRows = ref(itemsTyped.length)
|
||||
const currentPage = ref(1)
|
||||
const perPage = ref(5)
|
||||
const sortBy = ref([])
|
||||
const sortDirection = ref('asc')
|
||||
const filter = ref('')
|
||||
const filterOn = ref([])
|
||||
const infoModal = reactive({
|
||||
open: false,
|
||||
id: 'info-modal',
|
||||
title: '',
|
||||
content: '',
|
||||
})
|
||||
|
||||
// Create an options list from our fields
|
||||
const sortOptions = computed(() =>
|
||||
fieldsTyped.filter((f) => f.sortable).map((f) => ({text: f.label, value: f.key}))
|
||||
)
|
||||
|
||||
function info(item, index) {
|
||||
infoModal.title = `Row index: ${index}`
|
||||
infoModal.content = JSON.stringify(item, null, 2)
|
||||
infoModal.open = true
|
||||
}
|
||||
|
||||
function resetInfoModal() {
|
||||
infoModal.title = ''
|
||||
infoModal.content = ''
|
||||
}
|
||||
|
||||
function onFiltered(filteredItems) {
|
||||
// Trigger pagination to update the number of buttons/pages due to filtering
|
||||
totalRows.value = filteredItems.length
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
function onAddSort() {
|
||||
sortBy.value.push({key: '', order: 'asc'})
|
||||
}
|
||||
</script>
|
||||
160
frontend/js/views/UsersView.vue
Normal file
160
frontend/js/views/UsersView.vue
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<template>
|
||||
<BContainer fluid class="p-0">
|
||||
<BTableSimple caption-top responsive v-if="data !== null">
|
||||
<BThead>
|
||||
<BTr>
|
||||
<BTh v-for="field in fields" :width="field.width" class="cursor" :class="field.classes" @click="doSort(field.key)">
|
||||
<SortButton :currentOrder="order" :currentSort="sort" :order="field.key" :label="field.label" />
|
||||
</BTh>
|
||||
</Btr>
|
||||
</BThead>
|
||||
<BTbody>
|
||||
<BTr v-for="row in data.rows">
|
||||
<BTd v-for="field in fields" @click="doEdit(row)" class="cursor">
|
||||
{{ row[field.key] }}
|
||||
</BTd>
|
||||
</Btr>
|
||||
</BTbody>
|
||||
</BTableSimple>
|
||||
<BModal v-if="form !== null" v-model="formShow" :title="form?.label" @ok="doSave">
|
||||
<BAlert :model-value="form.error !== null" variant="danger" v-text="form.error"></BAlert>
|
||||
<BForm @submit="doSave">
|
||||
<BFormGroup
|
||||
class="mb-2"
|
||||
v-for="(field, key) in form.fields"
|
||||
:id="'form-label-' + key"
|
||||
:label="field.label"
|
||||
:label-for="'form-label-' + key"
|
||||
:description="field.description"
|
||||
>
|
||||
<BFormInput
|
||||
:id="'form-input-' + key"
|
||||
v-model="form.data[field.key]"
|
||||
:type="form.type"
|
||||
:required="form.required"
|
||||
/>
|
||||
</BFormGroup>
|
||||
</BForm>
|
||||
</BModal>
|
||||
</BContainer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
BTbody,
|
||||
BThead,
|
||||
BTr,
|
||||
BTd,
|
||||
BTh,
|
||||
BRow,
|
||||
BContainer,
|
||||
BTableSimple,
|
||||
BModal,
|
||||
BButton,
|
||||
BForm,
|
||||
BFormGroup,
|
||||
BFormInput,
|
||||
BAlert,
|
||||
} from 'bootstrap-vue-next'
|
||||
|
||||
import SortButton from './../components/SortButton.vue'
|
||||
import {ref, onMounted, reactive} from 'vue'
|
||||
|
||||
const data = ref(null)
|
||||
const order = ref(null)
|
||||
const sort = ref(null)
|
||||
const page = ref(null)
|
||||
const pages = ref(null)
|
||||
const limit = ref(null)
|
||||
const form = ref(null)
|
||||
const formShow = ref(false)
|
||||
|
||||
const refresh = (query) => {
|
||||
fetch(`/api/user?${new URLSearchParams(query)}`)
|
||||
.then(function(response) {
|
||||
return response.json()
|
||||
})
|
||||
.then(function(value) {
|
||||
data.value = value
|
||||
order.value = value.order
|
||||
sort.value = value.sort
|
||||
page.value = value.page
|
||||
pages.value = value.total_pages
|
||||
limit.value = value.limit
|
||||
})
|
||||
}
|
||||
|
||||
const doEdit = (item) => {
|
||||
form.value = {
|
||||
action: `api/user/?${item.id}`,
|
||||
method: 'POST',
|
||||
data: item,
|
||||
label: item.display_name,
|
||||
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 doSave = (e) => {
|
||||
console.log(form.value)
|
||||
}
|
||||
|
||||
const doSort = (key) => {
|
||||
let nextSort = 'asc'
|
||||
|
||||
if (order.value === key) {
|
||||
nextSort = (sort.value === 'asc' ? 'desc' : 'asc')
|
||||
}
|
||||
|
||||
refresh({
|
||||
order: key,
|
||||
sort: nextSort,
|
||||
limit: limit,
|
||||
page: 1,
|
||||
})
|
||||
|
||||
sort.value = key
|
||||
}
|
||||
|
||||
const fields = [
|
||||
{
|
||||
key: 'id',
|
||||
label: 'ID',
|
||||
width: '70px',
|
||||
},
|
||||
{
|
||||
key: 'display_name',
|
||||
label: 'Nom',
|
||||
width: '30%',
|
||||
},
|
||||
{
|
||||
key: 'username',
|
||||
label: 'Utilisateur',
|
||||
},
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
refresh({})
|
||||
})
|
||||
</script>
|
||||
24
frontend/scss/main.scss
Normal file
24
frontend/scss/main.scss
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
@import "~bootstrap/scss/bootstrap";
|
||||
@import "~bootstrap-vue-next/dist/bootstrap-vue-next.css";
|
||||
@import "~@fortawesome/fontawesome-free/css/all.css";
|
||||
|
||||
.gradient-custom {
|
||||
background: linear-gradient(to right, rgba(106, 17, 203, 1), rgba(37, 117, 252, 1));
|
||||
}
|
||||
|
||||
.cursor {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#app, main {
|
||||
min-height: 100vh;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
#nav {
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
#body {
|
||||
width: calc(100vw - 230px);
|
||||
}
|
||||
8
go.mod
8
go.mod
|
|
@ -13,9 +13,16 @@ require (
|
|||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/GeertJohan/go.rice v1.0.3 // indirect
|
||||
github.com/a-h/templ v0.2.778 // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/daaku/go.zipexe v1.0.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator v9.31.0+incompatible // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
|
|
@ -26,6 +33,7 @@ require (
|
|||
github.com/labstack/echo-contrib v0.17.1 // indirect
|
||||
github.com/labstack/echo/v4 v4.12.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
|
|
|
|||
21
go.sum
21
go.sum
|
|
@ -1,7 +1,11 @@
|
|||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.3 h1:k5viR+xGtIhF61125vCE1cmJ5957RQGXG6dmbaWZSmI=
|
||||
github.com/GeertJohan/go.rice v1.0.3/go.mod h1:XVdrU4pW00M4ikZed5q56tPf1v2KwnIKeIdc9CBYNt4=
|
||||
github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM=
|
||||
github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
|
|
@ -10,9 +14,21 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWs
|
|||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/daaku/go.zipexe v1.0.2 h1:Zg55YLYTr7M9wjKn8SY/WcpuuEi+kR2u4E8RhvpyXmk=
|
||||
github.com/daaku/go.zipexe v1.0.2/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/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/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA=
|
||||
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
|
|
@ -24,6 +40,7 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX
|
|||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
|
|
@ -34,6 +51,8 @@ github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+k
|
|||
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
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/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
|
|
@ -41,6 +60,7 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
|
|
@ -58,6 +78,7 @@ github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
|
|||
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
|
|
|
|||
7527
package-lock.json
generated
Normal file
7527
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
28
package.json
Normal file
28
package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@symfony/webpack-encore": "github:symfony/webpack-encore",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-vue-next": "^0.24.16",
|
||||
"raw-loader": "^4.0.2",
|
||||
"vue": "^3.4.29",
|
||||
"vue-template-compiler": "^2.7.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"babel-loader": "^9.1.3",
|
||||
"css-loader": "^7.1.2",
|
||||
"mini-css-extract-plugin": "^2.9.1",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"sass": "^1.78.0",
|
||||
"sass-loader": "^16.0.1",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"vue-loader": "^17.4.2",
|
||||
"vue-router": "^4.4.5",
|
||||
"webpack": "^5.94.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-notifier": "^1.15.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<!doctype html><html>
|
||||
<body><section class=\"vh-100 gradient-custom\"><div class=\"container py-5 h-100\"><div class=\"row d-flex justify-content-center align-items-center h-100\"><div class=\"col-12 col-md-8 col-lg-6 col-xl-5\"><div class=\"card bg-dark text-white\" style=\"border-radius: 1rem;\"><div class=\"card-body p-5\"><div class=\"mb-md-5 mt-md-4 pb-5\"><form action=\"/login\" method=\"POST\">
|
||||
<div class=\"alert alert-danger\">Mauvais identifiants.</div>
|
||||
<div class=\"form-outline form-white mb-4\"><label class=\"form-label\" for=\"username\">Nom d'utilisateur</label> <input type=\"text\" name=\"username\" id=\"username\" class=\"form-control form-control-lg\"></div><div class=\"form-outline form-white mb-4\"><label class=\"form-label\" for=\"password\">Mot de passe</label> <input type=\"password\" name=\"password\" id=\"password\" class=\"form-control form-control-lg\"></div><div class=\"d-grid gap-2\"><input class=\"btn btn-lg btn-primary\" type=\"submit\" value=\"Login\"></div></form></div></div></div></div></div></div></section>
|
||||
</body></html>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package template
|
||||
|
||||
templ Head(title string) {
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{ title }</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
<style>
|
||||
.gradient-custom {
|
||||
background: linear-gradient(to right, rgba(106, 17, 203, 1), rgba(37, 117, 252, 1))
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
}
|
||||
|
||||
templ JS() {
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
|
||||
<script src="main.js"></script>
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>
|
||||
</title><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN\" crossorigin=\"anonymous\"><style>\n\t\t\t.gradient-custom {\n\t\t\t\tbackground: linear-gradient(to right, rgba(106, 17, 203, 1), rgba(37, 117, 252, 1))\n\t\t\t}\n\t\t</style></head>
|
||||
<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js\" integrity=\"sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL\" crossorigin=\"anonymous\"></script><script src=\"main.js\"></script>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package home
|
||||
|
||||
import "gitnet.fr/deblan/budget/view/template"
|
||||
|
||||
templ Page() {
|
||||
<!doctype html>
|
||||
<html>
|
||||
@template.Head("Login")
|
||||
<body>
|
||||
Home!
|
||||
|
||||
@template.JS()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<!doctype html><html>
|
||||
<body>Home!
|
||||
</body></html>
|
||||
17
view/view.go
17
view/view.go
|
|
@ -1,17 +0,0 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"github.com/a-h/templ"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func Render(ctx echo.Context, statusCode int, t templ.Component) error {
|
||||
buf := templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(buf)
|
||||
|
||||
if err := t.Render(ctx.Request().Context(), buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.HTML(statusCode, buf.String())
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
package home
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"gitnet.fr/deblan/budget/database/model"
|
||||
"gitnet.fr/deblan/budget/view"
|
||||
"gitnet.fr/deblan/budget/view/template/home"
|
||||
"gitnet.fr/deblan/budget/web/view"
|
||||
"gitnet.fr/deblan/budget/web/view/template/app"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
|
|
@ -23,5 +23,5 @@ func (ctrl *Controller) HomeGet(c echo.Context) error {
|
|||
return c.Redirect(302, "/login")
|
||||
}
|
||||
|
||||
return view.Render(c, 200, home.Page())
|
||||
return view.Render(c, 200, app.Page())
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ import (
|
|||
"github.com/labstack/echo/v4"
|
||||
"gitnet.fr/deblan/budget/database/manager"
|
||||
"gitnet.fr/deblan/budget/database/model"
|
||||
"gitnet.fr/deblan/budget/view"
|
||||
"gitnet.fr/deblan/budget/view/template/auth"
|
||||
"gitnet.fr/deblan/budget/web/view"
|
||||
"gitnet.fr/deblan/budget/web/view/template/auth"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
|
|
|
|||
30
web/controller/crud/config.go
Normal file
30
web/controller/crud/config.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package crud
|
||||
|
||||
type Configuration struct {
|
||||
Model interface{}
|
||||
Models any
|
||||
ValidOrders []string
|
||||
ValidLimits []int
|
||||
DefaultLimit int
|
||||
CreateModel func() interface{}
|
||||
}
|
||||
|
||||
func (c *Configuration) IsValidOrder(value string) bool {
|
||||
for _, v := range c.ValidOrders {
|
||||
if v == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Configuration) IsValidLimit(value int) bool {
|
||||
for _, v := range c.ValidLimits {
|
||||
if v == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
159
web/controller/crud/crud.go
Normal file
159
web/controller/crud/crud.go
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package crud
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"gitnet.fr/deblan/budget/database/manager"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type UpdateCallback func(*gorm.DB, interface{}, interface{}) (interface{}, error)
|
||||
|
||||
type Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
Config Configuration
|
||||
}
|
||||
|
||||
func New() *Controller {
|
||||
c := Controller{}
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func (ctrl *Controller) With(config Configuration) *Controller {
|
||||
ctrl.Config = config
|
||||
return ctrl
|
||||
}
|
||||
|
||||
func (ctrl *Controller) List(c echo.Context) error {
|
||||
db := manager.Get().Db
|
||||
db = db.Model(ctrl.Config.Model)
|
||||
|
||||
order := c.QueryParam("order")
|
||||
sort := c.QueryParam("sort")
|
||||
|
||||
if !ctrl.Config.IsValidOrder(order) {
|
||||
order = "id"
|
||||
sort = "asc"
|
||||
}
|
||||
|
||||
db.Order(clause.OrderByColumn{
|
||||
Column: clause.Column{Name: order},
|
||||
Desc: sort == "desc",
|
||||
})
|
||||
|
||||
data := ListData{
|
||||
Limit: ctrl.Config.DefaultLimit,
|
||||
Order: order,
|
||||
Sort: sort,
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(c.QueryParam("limit"))
|
||||
|
||||
if err == nil && ctrl.Config.IsValidLimit(limit) {
|
||||
data.Limit = limit
|
||||
}
|
||||
|
||||
page, err := strconv.Atoi(c.QueryParam("page"))
|
||||
|
||||
if err == nil && page > 0 {
|
||||
data.Page = page
|
||||
}
|
||||
|
||||
ctrl.Paginate(&data, db)
|
||||
|
||||
return c.JSON(200, data)
|
||||
}
|
||||
|
||||
func (ctrl *Controller) Show(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).First(&item)
|
||||
|
||||
return c.JSON(200, item)
|
||||
}
|
||||
|
||||
func (ctrl *Controller) Update(c echo.Context, body interface{}, updateCallback UpdateCallback) 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).First(&item)
|
||||
|
||||
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 := updateCallback(db, item, body)
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(400, Error{
|
||||
Code: 400,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(200, result)
|
||||
}
|
||||
|
||||
func (ctrl *Controller) Paginate(data *ListData, db *gorm.DB) {
|
||||
var totalRows int64
|
||||
db.Model(ctrl.Config.Model).Count(&totalRows)
|
||||
|
||||
data.TotalRows = totalRows
|
||||
totalPages := int(math.Ceil(float64(totalRows) / float64(data.Limit)))
|
||||
data.TotalPages = totalPages
|
||||
|
||||
db.Offset(data.GetOffset())
|
||||
db.Limit(data.GetLimit())
|
||||
db.Find(&ctrl.Config.Models)
|
||||
|
||||
data.Rows = ctrl.Config.Models
|
||||
}
|
||||
30
web/controller/crud/pager.go
Normal file
30
web/controller/crud/pager.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package crud
|
||||
|
||||
type ListData struct {
|
||||
Limit int `json:"limit,omitempty;query:limit"`
|
||||
Page int `json:"page,omitempty;query:page"`
|
||||
TotalRows int64 `json:"total_rows"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
Rows interface{} `json:"rows"`
|
||||
Order string `json:"order"`
|
||||
Sort string `json:"sort"`
|
||||
}
|
||||
|
||||
func (p *ListData) GetOffset() int {
|
||||
return (p.GetPage() - 1) * p.GetLimit()
|
||||
}
|
||||
|
||||
func (p *ListData) GetLimit() int {
|
||||
if p.Limit == 0 {
|
||||
p.Limit = 10
|
||||
}
|
||||
return p.Limit
|
||||
}
|
||||
|
||||
func (p *ListData) GetPage() int {
|
||||
if p.Page == 0 {
|
||||
p.Page = 1
|
||||
}
|
||||
|
||||
return p.Page
|
||||
}
|
||||
78
web/controller/user/controller.go
Normal file
78
web/controller/user/controller.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package user
|
||||
|
||||
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.User{},
|
||||
Models: []model.User{},
|
||||
ValidOrders: []string{"id", "display_name", "username"},
|
||||
ValidLimits: []int{20, 50, 100},
|
||||
DefaultLimit: 20,
|
||||
CreateModel: func() interface{} {
|
||||
return new(model.User)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func New(e *echo.Echo) *Controller {
|
||||
c := Controller{
|
||||
crud: crud.New(),
|
||||
}
|
||||
|
||||
e.GET("/api/user", c.List)
|
||||
e.GET("/api/user/:id", c.Show)
|
||||
e.POST("/api/user/:id", c.Update)
|
||||
|
||||
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) Update(c echo.Context) error {
|
||||
if nil == model.LoadSessionUser(c) {
|
||||
return c.Redirect(302, "/login")
|
||||
}
|
||||
|
||||
type body struct {
|
||||
DisplayName string `json:"display_name" form:"display_name" validate:"required"`
|
||||
Password string `json:"password" form:"password"`
|
||||
}
|
||||
|
||||
return ctrl.crud.With(ctrl.Config()).Update(c, new(body), func(db *gorm.DB, a, b interface{}) (interface{}, error) {
|
||||
item := a.(*model.User)
|
||||
update := b.(*body)
|
||||
item.DisplayName = update.DisplayName
|
||||
|
||||
if update.Password != "" {
|
||||
item.UpdatePassword(update.Password)
|
||||
}
|
||||
|
||||
db.Model(ctrl.crud.Config.Model).Where("id = ?", item.ID).Save(&item)
|
||||
|
||||
return item, nil
|
||||
})
|
||||
}
|
||||
|
|
@ -2,11 +2,13 @@ package router
|
|||
|
||||
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/home"
|
||||
"gitnet.fr/deblan/budget/web/controller/user"
|
||||
)
|
||||
|
||||
func RegisterControllers(e *echo.Echo) {
|
||||
auth.New(e)
|
||||
home.New(e)
|
||||
app.New(e)
|
||||
user.New(e)
|
||||
}
|
||||
|
|
|
|||
15
web/view/template/app/page.templ
Normal file
15
web/view/template/app/page.templ
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package app
|
||||
|
||||
import "gitnet.fr/deblan/budget/web/view/template"
|
||||
|
||||
templ Page() {
|
||||
<!doctype html>
|
||||
<html>
|
||||
@template.Head("Budget")
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@template.JS()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
|
@ -1,18 +1,21 @@
|
|||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.747
|
||||
package home
|
||||
// templ: version: v0.2.778
|
||||
package app
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "gitnet.fr/deblan/budget/view/template"
|
||||
import "gitnet.fr/deblan/budget/web/view/template"
|
||||
|
||||
func Page() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
|
|
@ -28,15 +31,15 @@ func Page() templ.Component {
|
|||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = template.Head("Login").Render(ctx, templ_7745c5c3_Buffer)
|
||||
templ_7745c5c3_Err = template.Head("Budget").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body><div id=\"app\"></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -44,10 +47,12 @@ func Page() templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package auth
|
||||
|
||||
import "gitnet.fr/deblan/budget/view/template"
|
||||
import "gitnet.fr/deblan/budget/web/view/template"
|
||||
|
||||
templ Page(hasError bool) {
|
||||
<!doctype html>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.747
|
||||
// templ: version: v0.2.778
|
||||
package auth
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
|
@ -8,11 +8,14 @@ package auth
|
|||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "gitnet.fr/deblan/budget/view/template"
|
||||
import "gitnet.fr/deblan/budget/web/view/template"
|
||||
|
||||
func Page(hasError bool) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
|
|
@ -28,7 +31,7 @@ func Page(hasError bool) templ.Component {
|
|||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -36,17 +39,17 @@ func Page(hasError bool) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body><section class=\"vh-100 gradient-custom\"><div class=\"container py-5 h-100\"><div class=\"row d-flex justify-content-center align-items-center h-100\"><div class=\"col-12 col-md-8 col-lg-6 col-xl-5\"><div class=\"card bg-dark text-white\" style=\"border-radius: 1rem;\"><div class=\"card-body p-5\"><div class=\"mb-md-5 mt-md-4 pb-5\"><form action=\"/login\" method=\"POST\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if hasError {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"alert alert-danger\">Mauvais identifiants.</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"form-outline form-white mb-4\"><label class=\"form-label\" for=\"username\">Nom d'utilisateur</label> <input type=\"text\" name=\"username\" id=\"username\" class=\"form-control form-control-lg\"></div><div class=\"form-outline form-white mb-4\"><label class=\"form-label\" for=\"password\">Mot de passe</label> <input type=\"password\" name=\"password\" id=\"password\" class=\"form-control form-control-lg\"></div><div class=\"d-grid gap-2\"><input class=\"btn btn-lg btn-primary\" type=\"submit\" value=\"Login\"></div></form></div></div></div></div></div></div></section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -54,10 +57,12 @@ func Page(hasError bool) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
16
web/view/template/base.templ
Normal file
16
web/view/template/base.templ
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package template
|
||||
|
||||
import "gitnet.fr/deblan/budget/web/view"
|
||||
|
||||
templ Head(title string) {
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{ title }</title>
|
||||
@templ.Raw(view.EntrypointCss("main"))
|
||||
</head>
|
||||
}
|
||||
|
||||
templ JS() {
|
||||
@templ.Raw(view.EntrypointJs("main"))
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.747
|
||||
// templ: version: v0.2.778
|
||||
package template
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
|
@ -8,9 +8,14 @@ package template
|
|||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "gitnet.fr/deblan/budget/web/view"
|
||||
|
||||
func Head(title string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
|
|
@ -26,20 +31,28 @@ func Head(title string) templ.Component {
|
|||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/template/base.templ`, Line: 7, Col: 16}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/view/template/base.templ`, Line: 9, Col: 16}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</title>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(view.EntrypointCss("main")).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</head>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -50,6 +63,9 @@ func Head(title string) templ.Component {
|
|||
func JS() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
|
|
@ -65,10 +81,12 @@ func JS() templ.Component {
|
|||
templ_7745c5c3_Var3 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
|
||||
templ_7745c5c3_Err = templ.Raw(view.EntrypointJs("main")).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
87
web/view/view.go
Normal file
87
web/view/view.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed static/*
|
||||
statics embed.FS
|
||||
manifest map[string]string
|
||||
entrypoints map[string]map[string]map[string][]string
|
||||
)
|
||||
|
||||
func Render(ctx echo.Context, statusCode int, t templ.Component) error {
|
||||
buf := templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(buf)
|
||||
|
||||
if err := t.Render(ctx.Request().Context(), buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.HTML(statusCode, buf.String())
|
||||
}
|
||||
|
||||
func Asset(name string) string {
|
||||
if manifest == nil {
|
||||
value, _ := statics.ReadFile("static/manifest.json")
|
||||
json.Unmarshal(value, &manifest)
|
||||
}
|
||||
|
||||
path, ok := manifest[name]
|
||||
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func entrypointFiles(app, category string) []string {
|
||||
if entrypoints == nil {
|
||||
value, _ := statics.ReadFile("static/entrypoints.json")
|
||||
json.Unmarshal(value, &entrypoints)
|
||||
}
|
||||
|
||||
entry, ok := entrypoints["entrypoints"][app]
|
||||
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
files, ok := entry[category]
|
||||
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
func EntrypointJs(app string) string {
|
||||
files := entrypointFiles(app, "js")
|
||||
results := []string{}
|
||||
|
||||
for _, file := range files {
|
||||
results = append(results, fmt.Sprintf(`<script src="%s"></script>`, file))
|
||||
}
|
||||
|
||||
return strings.Join(results, "\n")
|
||||
}
|
||||
|
||||
func EntrypointCss(app string) string {
|
||||
files := entrypointFiles(app, "css")
|
||||
results := []string{}
|
||||
|
||||
for _, file := range files {
|
||||
results = append(results, fmt.Sprintf(`<link rel="stylesheet" href="%s" />`, file))
|
||||
}
|
||||
|
||||
return strings.Join(results, "\n")
|
||||
}
|
||||
73
webpack.config.js
Normal file
73
webpack.config.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
const Encore = require('@symfony/webpack-encore');
|
||||
|
||||
// Manually configure the runtime environment if not already configured yet by the "encore" command.
|
||||
// It's useful when you use tools that rely on webpack.config.js file.
|
||||
if (!Encore.isRuntimeEnvironmentConfigured()) {
|
||||
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
|
||||
}
|
||||
|
||||
Encore
|
||||
// directory where compiled assets will be stored
|
||||
.setOutputPath('web/view/static')
|
||||
// public path used by the web server to access the output path
|
||||
.setPublicPath('/static')
|
||||
// only needed for CDN's or subdirectory deploy
|
||||
//.setManifestKeyPrefix('build/')
|
||||
|
||||
/*
|
||||
* ENTRY CONFIG
|
||||
*
|
||||
* Each entry will result in one JavaScript file (e.g. app.js)
|
||||
* and one CSS file (e.g. app.css) if your JavaScript imports CSS.
|
||||
*/
|
||||
.addEntry('main', './frontend/js/main.js')
|
||||
|
||||
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
|
||||
.splitEntryChunks()
|
||||
|
||||
// will require an extra script tag for runtime.js
|
||||
// but, you probably want this, unless you're building a single-page app
|
||||
.enableSingleRuntimeChunk()
|
||||
|
||||
/*
|
||||
* FEATURE CONFIG
|
||||
*
|
||||
* Enable & configure other features below. For a full
|
||||
* list of features, see:
|
||||
* https://symfony.com/doc/current/frontend.html#adding-more-features
|
||||
*/
|
||||
.cleanupOutputBeforeBuild()
|
||||
// .enableBuildNotifications()
|
||||
.enableVueLoader(() => {}, {
|
||||
})
|
||||
.enableSourceMaps(!Encore.isProduction())
|
||||
// enables hashed filenames (e.g. app.abc123.css)
|
||||
.enableVersioning(Encore.isProduction())
|
||||
|
||||
.configureBabel((config) => {
|
||||
config.plugins.push('@babel/plugin-syntax-dynamic-import');
|
||||
})
|
||||
|
||||
// .copyFiles({
|
||||
// from: './frontend/images',
|
||||
// to: 'images/[path][name].[hash:8].[ext]'
|
||||
// })
|
||||
|
||||
// enables Sass/SCSS support
|
||||
.enableSassLoader()
|
||||
|
||||
// uncomment if you use TypeScript
|
||||
//.enableTypeScriptLoader()
|
||||
|
||||
// uncomment if you use React
|
||||
//.enableReactPreset()
|
||||
|
||||
// uncomment to get integrity="..." attributes on your script & link tags
|
||||
// requires WebpackEncoreBundle 1.4 or higher
|
||||
//.enableIntegrityHashes(Encore.isProduction())
|
||||
|
||||
// uncomment if you're having problems with a jQuery plugin
|
||||
//.autoProvidejQuery()
|
||||
;
|
||||
|
||||
module.exports = Encore.getWebpackConfig();
|
||||
Loading…
Add table
Add a link
Reference in a new issue