Compare commits

...

36 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
9ce55782c6 add workflow_dispatch for api trigger
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 7s
2026-01-12 22:30:23 +01:00
Julien Aldon
959d4e7281 remove debug
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 6s
2026-01-12 16:24:12 +01:00
Julien Aldon
626fbaa269 test deploy
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 7s
2026-01-12 15:23:52 +01:00
Julien Aldon
eef53216de test deploy
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 7s
2026-01-12 15:22:37 +01:00
Julien Aldon
fae951d9f4 fix environment phase
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 6s
2026-01-12 14:19:51 +01:00
Julien Aldon
098c442bb9 add debug
All checks were successful
Deploy Bookshelf app / deploy (push) Successful in 5s
2026-01-12 14:13:05 +01:00
Julien Aldon
f2e642d376 add debug
Some checks failed
Deploy Bookshelf app / deploy (push) Has been cancelled
2026-01-12 14:10:59 +01:00
Julien Aldon
bbbc054c54 add debug
Some checks failed
Deploy Bookshelf app / deploy (push) Has been cancelled
2026-01-12 14:01:42 +01:00
Julien Aldon
9a6e443c44 add debug
Some checks failed
Deploy Bookshelf app / deploy (push) Failing after 5s
2026-01-12 14:00:09 +01:00
Julien Aldon
ccaf1bc039 add volume to deploy
Some checks failed
Deploy Bookshelf app / deploy (push) Failing after 4s
2026-01-12 13:57:45 +01:00
Julien Aldon
5c1e3fe68f fix deploy 2026-01-12 13:56:43 +01:00
Julien Aldon
39890a02ab fix deploy 2026-01-12 13:56:15 +01:00
Julien Aldon
bab1954ed0 fix deploy 2026-01-12 13:54:16 +01:00
Julien Aldon
75df01b8f6 add environment setup phase
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 6s
2026-01-12 13:25:55 +01:00
Julien Aldon
12788c6109 add environment setup phase
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 7s
2026-01-12 13:24:14 +01:00
Julien Aldon
531ac4b85f add environment setup phase
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 8s
2026-01-12 13:22:42 +01:00
Julien Aldon
9ce4810f25 add environment setup phase 2026-01-12 13:22:09 +01:00
Julien Aldon
2b6b9794b0 fix deploy action
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 6s
2026-01-12 13:18:01 +01:00
Julien Aldon
69bc74835b fix .env
Some checks failed
Deploy Bookshelf / deploy (push) Failing after 5s
2026-01-12 13:16:39 +01:00
Julien Aldon
50d486a3a3 fix secret.py
Some checks failed
Deploy Bookshelf / deploy (push) Failing after 4s
2026-01-12 13:15:51 +01:00
Julien Aldon
164eec1997 fix secret.py
Some checks failed
Deploy Bookshelf / deploy (push) Failing after 5s
2026-01-12 13:12:10 +01:00
Julien Aldon
5d8c7b0724 fix docker compose and deploy.yaml
Some checks failed
Deploy Bookshelf / deploy (push) Failing after 5s
2026-01-12 13:10:10 +01:00
Julien Aldon
93eabcb75e fix docker-compose env file path
Some checks failed
Deploy Bookshelf / deploy (push) Failing after 4s
2026-01-12 13:04:37 +01:00
Julien Aldon
fd49074614 fix docker-compose env file path
Some checks failed
Deploy Bookshelf / deploy (push) Failing after 4s
2026-01-12 13:04:10 +01:00
Julien Aldon
02f434a6ff fix docker-compose env file path
Some checks failed
Deploy Bookshelf / deploy (push) Failing after 4s
2026-01-12 13:03:00 +01:00
Julien Aldon
e3b94ceaeb add debug
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 6s
2026-01-12 12:23:13 +01:00
e657581220 remove dist folder
All checks were successful
Deploy Bookshelf / deploy (push) Successful in 7s
2026-01-12 11:33:22 +01:00
20 changed files with 167 additions and 57 deletions

10
.env.example Normal file
View File

@@ -0,0 +1,10 @@
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,6 +3,7 @@ on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
@@ -10,7 +11,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build & deploy
run: |
git pull

View File

@@ -3,16 +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
certbot certonly --dns-ovh --dns-ovh-credentials ~/.secrets/certbot/ovh.ini -d bookshelf.aldon.fr
```
## TODO
Change `front/src/config.js` : Variabilize `ROOT_FQDN`

View File

@@ -1,15 +1,15 @@
import os
origins = [
os.environ['ORIGIN']
os.environ['SERVICE_ORIGIN']
]
host = os.environ['DB_HOST']
user = os.environ['DB_USER']
password = os.environ['DB_PASS']
database = os.environ['DB_NAME']
user = os.environ['MARIADB_USER']
password = os.environ['MARIADB_PASSWORD']
database = os.environ['MARIADB_DATABASE']
# openssl rand -hex 32
SECRET_KEY = os.environ['SECRET_KEY']
SECRET_KEY = os.environ['SERVICE_SECRET_KEY']
ALGORITHM = 'HS256'
ACCESS_TOKEN_EXPIRE_MINUTES = 600

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:
@@ -14,12 +17,12 @@ services:
dockerfile: back/Dockerfile
restart: always
environment:
ORIGIN: ${SERVICE_ORIGIN}
SERVICE_ORIGIN: ${SERVICE_ORIGIN}
DB_HOST: database
DB_USER: ${MARIADB_USER}
DB_PASS: ${MARIADB_PASSWORD}
DB_NAME: ${MARIADB_DATABASE}
SECRET_KEY: ${SERVICE_SECRET_KEY}
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
SERVICE_SECRET_KEY: ${SERVICE_SECRET_KEY}
ports:
- 8000:8000
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

@@ -1 +0,0 @@
.red{background-color:#de1656;color:#fff}.red:hover{background-color:#961656}.green{background-color:#16de56}.green:hover{background-color:#169656}header{width:60.5vw}.header,header{display:flex}body{margin:0}.button{border:none;padding:.2rem;margin:.3rem}.view{display:flex;flex-direction:column;align-items:center;margin-top:5rem}.router{flex:1 1 auto;background-color:#de1656;border:none;color:#fff;text-align:center;text-decoration:none;display:inline-block;font-size:16px;transition:background-color .5s ease-in}.router:focus,.router:hover{background-color:#961656}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50}nav a{padding:1rem;margin:0}table{table-layout:fixed}.button-panel{display:flex;flex-flow:row}tr:nth-child(odd){background-color:hsla(0,0%,4%,.1)}tr{text-align:left}tr:hover{background-color:hsla(0,0%,4%,.1)}h1{flex:1 1 auto;text-align:left}td{overflow:hidden;text-overflow:ellipsis;word-wrap:break-word}.AddButton{border:none;margin-right:.2rem;justify-content:flex-end;align-self:center}.AddButton,.edit{height:2rem;width:2rem}.AddForm{display:flex;flex-direction:column;overflow:hidden;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-name:lineInsertedIn;animation-name:lineInsertedIn;transition:height .3s}.AddForm input{margin:1rem}.AddForm .button{align-self:center;align-self:flex-end}.AddForm label{align-self:flex-start}th[data-v-aa4cb3dc]{text-align:left}.elipsis[data-v-6b7294ba]{font-size:3rem}.page[data-v-6b7294ba]{border:none;padding:1rem;margin:.2rem}footer[data-v-6b7294ba]{display:flex;padding:.2rem;align-items:center}.searchBox[data-v-559755d8]{display:flex}.search[data-v-559755d8]{display:flex;min-width:95%;margin-top:1rem;margin-bottom:1rem}.searchButton[data-v-559755d8]{color:hsla(0,0%,4%,.8);margin-left:-1.1rem;width:1rem;height:1rem;align-self:center;align-items:center;font-size:.6rem;border:none}select[data-v-1faa4947]{max-width:14.5rem}.dropdown[data-v-1faa4947]{background-color:transparent}i[data-v-1faa4947]:hover{color:#de1656}.dropdown-content[data-v-1faa4947]{position:absolute;background-color:#f8f8f8;min-width:14em;max-width:15rem;max-height:15rem;margin-top:.5rem;border:1px solid #de1656;box-shadow:0 -8px 34px 0 rgba(0,0,0,.05);overflow:auto;z-index:1}.dropdown-input[data-v-1faa4947]{width:14em;margin-right:1em}.dropdown-item[data-v-1faa4947]{color:#000;line-height:1em;text-decoration:none;padding:.5em;display:block;cursor:pointer}main[data-v-68f1643b]{max-width:60.5vw}td input[data-v-68f1643b]{width:14.5rem}@-webkit-keyframes lineInsertedIn-68f1643b{0%{height:0}to{height:23rem}}@keyframes lineInsertedIn-68f1643b{0%{height:0}to{height:23rem}}a[data-v-f3fce040]{text-decoration:none}h2[data-v-f3fce040],p[data-v-f3fce040]{text-align:left}a[data-v-31c3b6b4]{text-decoration:none}h2[data-v-31c3b6b4],p[data-v-31c3b6b4]{text-align:left}main[data-v-590e11e2]{width:80vw}table[data-v-590e11e2]{margin-left:auto;margin-right:auto}.elipsis[data-v-590e11e2]{font-size:3rem}.page[data-v-590e11e2]{border:none;padding:1rem;margin:.2rem}footer[data-v-590e11e2]{display:flex;padding:.2rem;align-items:center}button[data-v-3cf27bac]{margin-top:.5rem}label[data-v-3cf27bac]{margin:.2rem;text-align:left}input[data-v-3cf27bac]{border:none}input[data-v-3cf27bac],input[data-v-3cf27bac]:hover{background-color:rgba(0,0,0,.1)}main[data-v-3cf27bac]{width:20rem;align-self:center;align-content:center}div[data-v-3cf27bac],main[data-v-3cf27bac]{display:flex;flex-direction:column}

BIN
front/dist/favicon.ico vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1 +0,0 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><script src="https://kit.fontawesome.com/08db035529.js" crossorigin="anonymous"></script><title>Aldon's Home</title><script defer="defer" src="/js/chunk-vendors.6bc46727.js"></script><script defer="defer" src="/js/app.10597b7c.js"></script><link href="/css/app.f7195e87.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but Aldon's Home doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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 {