Compare commits

...

9 Commits

Author SHA1 Message Date
Julien Aldon
84369911cd add restart always to nginx service
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 6s
2026-01-14 13:49:17 +01:00
Julien Aldon
443cfd7615 add front responsiveness for phone
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 20s
2026-01-13 17:02:16 +01:00
Julien Aldon
ed22dfd412 fix docker-compose version
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 36s
2026-01-13 16:03:08 +01:00
Julien Aldon
aec31fbd06 fix docker-compose version
Some checks failed
Deploy Bookshelf / deploy (push) Failing after 5s
2026-01-13 15:59:28 +01:00
Julien Aldon
2e5aeb46ab fix docker-compose prod ROOT_FQDN var
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 36s
2026-01-13 15:57:07 +01:00
Julien Aldon
b718865030 add ROOT_FQDN env variable
Some checks failed
Deploy Bookshelf / deploy (push) Failing after 5s
2026-01-13 15:55:32 +01:00
Julien Aldon
e8208363c0 fix front config.js
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 19s
2026-01-13 15:28:26 +01:00
Julien Aldon
f8f37db3d6 Merge branch 'main' of gitea.aldon.fr:Mop/bookshelf
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 52s
2026-01-13 15:26:20 +01:00
Julien Aldon
d5fc8142b2 fix front pagination and search 2026-01-13 15:26:11 +01:00
11 changed files with 155 additions and 43 deletions

View File

@@ -1,7 +1,10 @@
SERVICE_ORIGIN
DB_HOST
MARIADB_USER
SERVICE_SECRET_KEY
MARIADB_PASSWORD
MARIADB_ROOT_PASSWORD
MARIADB_DATABASE
DB_HOST=database
MARIADB_USER=root
# openssl rand -hex 32
SERVICE_SECRET_KEY=xxxx
# openssl rand -hex 12
MARIADB_PASSWORD=xxxx
# openssl rand -hex 12
MARIADB_ROOT_PASSWORD=xxxx
MARIADB_DATABASE=bookshelf
SERVICE_ORIGIN=http://localhost:8080

View File

@@ -3,27 +3,22 @@ Ce projet est un gestionnaire de base de données pour la bibliotèque de la mai
L'outil permet l'ajout, la modification, la suppression et de consulter les livres présents dans la base de donnée.
## Deploiement
## Développement local
### Avec Docker Compose
```sh
docker-compose up --build -d
docker compose -f docker-compose.dev.yml down -v
docker compose -f docker-compose.dev.yml up
# Init database
# Init database (if database not initialized)
docker-compose exec database mariadb -u root -p
$>source /docker-entrypoint-initdb.d/*.sql
```
## Déploiement
```sh
# SSL https://certbot-dns-ovh.readthedocs.io/en/stable/
# Create certificates
certbot certonly --dns-ovh --dns-ovh-credentials ~/.secrets/certbot/ovh.ini -d home.aldon.fr
```
# Set env variables inside secrets action
```sh
SERVICE_ORIGIN
DB_HOST
SERVICE_SECRET_KEY
MARIADB_USER
MARIADB_PASSWORD
MARIADB_ROOT_PASSWORD
MARIADB_DATABASE
certbot certonly --dns-ovh --dns-ovh-credentials ~/.secrets/certbot/ovh.ini -d bookshelf.aldon.fr
```
## TODO
Change `front/src/config.js` : Variabilize `ROOT_FQDN`

52
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,52 @@
version: "3.9"
services:
back:
image: python:3.10
working_dir: /code
volumes:
- ./back:/code
command: >
sh -c "pip install -r requirements.txt &&
uvicorn main:app --host 0.0.0.0 --port 8000 --reload"
environment:
SERVICE_ORIGIN: ${SERVICE_ORIGIN}
DB_HOST: database
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
SERVICE_SECRET_KEY: ${SERVICE_SECRET_KEY}
ports:
- "8000:8000"
depends_on:
- database
front:
image: node:19.1-alpine
working_dir: /app
volumes:
- ./front:/app
command: sh -c "npm install && npm run serve"
ports:
- "8080:8080"
environment:
VUE_APP_ROOT_FQDN: ${SERVICE_ROOT_FQDN}
depends_on:
- back
database:
image: mariadb
restart: always
environment:
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
volumes:
- db:/var/lib/mysql
- ./back/db/:/docker-entrypoint-initdb.d/
ports:
- "3306:3306"
volumes:
db:

View File

@@ -1,9 +1,12 @@
version: "2"
version: "3.9"
services:
nginx:
restart: always
build:
context: .
dockerfile: front/Dockerfile
args:
VUE_APP_ROOT_FQDN: ${SERVICE_ROOT_FQDN}
ports:
- 80:80
depends_on:
@@ -32,6 +35,7 @@ services:
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
ROOT_FQDN: ${ROOT_FQDN}
volumes:
- db:/var/lib/mysql
- ./back/db/:/docker-entrypoint-initdb.d/

View File

@@ -2,6 +2,9 @@ FROM node:19.1-alpine AS build
WORKDIR /app
ARG VUE_APP_ROOT_FQDN
ENV VUE_APP_ROOT_FQDN=$VUE_APP_ROOT_FQDN
COPY front/package.json front/package-lock.json /app/
RUN npm install

View File

@@ -85,7 +85,6 @@ body {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 5rem;
}
.router {

View File

@@ -4,18 +4,18 @@
<button class="page" v-for="index in $store.getters.getBookCurrentPage + 1" :key="index">{{ index }}</button>
</footer>
<footer v-else>
<button @click="$store.dispatch('fetchPageBooks', {page: 0, nooption: false})" class="page"> {{ '<<' }} </button>
<button @click="$store.dispatch('fetchPageBooks', {page: 0, nooption: false, ...$route.query})" class="page"> {{ '<<' }} </button>
<button
class="page"
:class="{ 'red': $store.getters.getBookCurrentPage - Math.max(0, Math.min($store.getters.getBookCurrentPage - 2, $store.getters.getBookPageNb - 5)) === index}"
v-for="(n, index) in (Math.max(5, Math.min($store.getters.getBookCurrentPage + 2, $store.getters.getBookPageNb)) - Math.max(0, Math.min($store.getters.getBookCurrentPage - 2, $store.getters.getBookPageNb - 5)))"
:key="index"
@click="$store.dispatch('fetchPageBooks', {page: Math.max(0, Math.min($store.getters.getBookCurrentPage - 2, $store.getters.getBookPageNb - 5)) + index, nooption: false})">
@click="$store.dispatch('fetchPageBooks', {page: Math.max(0, Math.min($store.getters.getBookCurrentPage - 2, $store.getters.getBookPageNb - 5)) + index, nooption: false, ...$route.query})">
{{ Math.max(0, Math.min($store.getters.getBookCurrentPage - 2, $store.getters.getBookPageNb - 5)) + index }}
</button>
<p v-if="$store.getters.getBookCurrentPage !== $store.getters.getBookPageNb" class="elipsis">...</p>
<button v-if="$store.getters.getBookCurrentPage !== $store.getters.getBookPageNb" class="page" @click="$store.dispatch('fetchPageBooks', {page: $store.getters.getBookPageNb, nooption: false})">{{ $store.getters.getBookPageNb }}</button>
<button @click="$store.dispatch('fetchPageBooks', {page: $store.getters.getBookPageNb, nooption: false})" class="page"> >> </button>
<button v-if="$store.getters.getBookCurrentPage !== $store.getters.getBookPageNb" class="page" @click="$store.dispatch('fetchPageBooks', {page: $store.getters.getBookPageNb, nooption: false, ...$route.query})">{{ $store.getters.getBookPageNb }}</button>
<button @click="$store.dispatch('fetchPageBooks', {page: $store.getters.getBookPageNb, nooption: false, ...$route.query})" class="page"> >> </button>
</footer>
</div>
</template>
@@ -26,7 +26,7 @@
.page {
border: none;
padding: 1rem;
padding: 0.5rem;
margin: 0.2rem;
}
@@ -34,6 +34,7 @@ footer {
display: flex;
padding: 0.2rem;
align-items: center;
max-width: 100%;
}

View File

@@ -5,6 +5,7 @@
</div>
</template>
<script>
import router from '@/router';
export default {
props: {
page: String,
@@ -12,6 +13,7 @@ export default {
watch: {
search: function() {
this.$emit('changeSearch', this.search);
router.push({ path: 'books', query: { search: this.search }})
this.$store.dispatch('fetchPage'+this.page, {page: 0, nooption: false, search: this.search, order: this.order})
}
},
@@ -35,7 +37,7 @@ export default {
.search {
display: flex;
min-width: 95%;
width: 100vw;
margin-top: 1rem;
margin-bottom: 1rem;
}

View File

@@ -1,7 +1,7 @@
<template>
<table>
<tr>
<th v-bind:style="{ 'min-width': this.width }" v-for="head in headings" :key="head">{{ head }}</th>
<th v-for="head in headings" :key="head">{{ head }}</th>
</tr>
<slot></slot>
</table>
@@ -19,5 +19,25 @@ export default {
<style scoped>
th {
text-align: left;
width: 20%;
}
tr {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
table {
display: flex;
flex-direction: column;
}
@media (max-width: 30em) {
tr {
width: 100vw;
}
}
</style>

View File

@@ -1,5 +1,4 @@
const ROOT_FQDN = 'https://bookshelf.aldon.fr/api';
// const ROOT_FQDN = 'http://localhost:8000/api';
const ROOT_FQDN = process.env.VUE_APP_ROOT_FQDN
export {
ROOT_FQDN

View File

@@ -20,7 +20,7 @@
</button>
</form>
<SearchHeader page="Books" @changeSearch="(s) => {editSearch(s)}"/>
<TableView ref="table" :headings="headings" width="15rem">
<TableView ref="table" :headings="headings">
<tr v-for="book in this.$store.getters.getBooks" :key="book">
<td v-if="editRow === book.biblio_Index">
<DropDown
@@ -28,7 +28,7 @@
field="Auteur"
:inputValue="book.Auteur"
@selected="(n) => {formEdit.author = n}"
/>
/>
</td>
<td v-else>{{ book.Auteur }}</td>
<td v-if="editRow === book.biblio_Index">
@@ -37,7 +37,7 @@
field="Titre"
:inputValue="book.Titre"
@selected="(n) => {formEdit.title = n}"
/>
/>
</td>
<td v-else>{{ book.Titre }}</td>
<td v-if="editRow === book.biblio_Index">
@@ -46,7 +46,7 @@
field="Editeur"
:inputValue="book.Editeur"
@selected="(n) => {formEdit.editor = n}"
/>
/>
</td>
<td v-else>{{ book.Editeur }}</td>
<td v-if="editRow === book.biblio_Index">
@@ -55,7 +55,7 @@
field="Type"
:inputValue="book.Type"
@selected="(n) => {formEdit.type = n}"
/>
/>
</td>
<td v-else>{{ book.Type }}</td>
<div class="button-panel">
@@ -87,7 +87,7 @@ export default {
},
data() {
return {
headings: ['Author', 'Title', 'Editor', 'Type'],
headings: ['Author', 'Title', 'Editor', 'Type', 'Control'],
editMode: false,
editRow: undefined,
addMode: false,
@@ -188,11 +188,45 @@ export default {
</script>
<style scoped>
main {
max-width: 60.5vw;
width: 80vw
}
td input {
width: 14.5rem;
header {
width: 80vw;
}
tr {
display: flex;
flex-direction: row;
justify-content: flex-start;
width: 80vw;
align-items: center;
}
td {
width: 20%;
}
.button-panel {
width: 20%;
display: flex;
flex-direction: flex-end;
}
@media (max-width: 30em) {
main {
width: 100vw;
}
tr {
width: 100vw;
}
td {
overflow-x: hidden;
white-space: nowrap
}
}
@keyframes lineInsertedIn {