mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-11-09 19:20:09 +01:00
Nginx config, working deploy, basic theme, repo cleanup
This commit is contained in:
1 parent
2cb5436647
commit
f23fbb481b
30 files changed
+4536
-760
No files matched your search
+544
-327
@@ -1,342 +1,559 @@
|
|||||||
@font-face {
|
/* ============================================
|
||||||
font-family: police1;
|
POST-ERG - Minimalistic CSS
|
||||||
src: url("fonts/Combinedd.otf");
|
============================================ */
|
||||||
|
|
||||||
|
/* Custom Font */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Combined';
|
||||||
|
src: url("fonts/Combinedd.otf");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
VARIABLES & BASE
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-primary: #c104fc;
|
||||||
|
--color-secondary: #4da870;
|
||||||
|
--color-text: #333;
|
||||||
|
--color-text-light: #666;
|
||||||
|
--color-border: #ddd;
|
||||||
|
--color-bg: #fff;
|
||||||
|
--color-bg-light: #f9f9f9;
|
||||||
|
--spacing-sm: 0.75rem;
|
||||||
|
--spacing: 1.5rem;
|
||||||
|
--spacing-lg: 3rem;
|
||||||
|
--spacing-xl: 4rem;
|
||||||
|
--border-radius: 8px;
|
||||||
|
--max-width: 1400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-bg-light);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
NAVBAR
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
font-family: 'Combined', sans-serif;
|
||||||
|
background: linear-gradient(280deg, var(--color-secondary) 0%, var(--color-primary) 85%);
|
||||||
|
padding: 2rem 3rem;
|
||||||
|
color: white;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-item {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-item:hover {
|
||||||
|
color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
LAYOUT
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding: var(--spacing-xl) var(--spacing-lg);
|
||||||
|
background: var(--color-bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: var(--max-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grid System */
|
||||||
|
.columns {
|
||||||
|
display: grid;
|
||||||
|
gap: 2rem;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns.is-multiline {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
min-width: 0; /* Fix overflow issues */
|
||||||
|
}
|
||||||
|
|
||||||
|
.column.is-one-third {
|
||||||
|
grid-column: span 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column.is-one-fifth {
|
||||||
|
/* Will use auto-fill for responsive grid */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Two-column layout for detail pages */
|
||||||
|
.columns.is-variable {
|
||||||
|
grid-template-columns: 1fr 2fr;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.columns.is-variable {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.navbar {
|
/* ============================================
|
||||||
font-family: 'police1';
|
CARDS
|
||||||
background: linear-gradient(280deg, rgba(77, 168, 112, 1) 0%, rgba(193, 4, 252, 1) 85%);
|
============================================ */
|
||||||
background-color: rgba(0, 0, 0, 0);
|
|
||||||
text-decoration: none;
|
.card {
|
||||||
outline: none;
|
background: var(--color-bg);
|
||||||
/* font-size: 1rem; */
|
border: 2px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-link:hover .card {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
box-shadow: 0 8px 24px rgba(193, 4, 252, 0.2);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 240px;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
padding: 1.75rem;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
TYPOGRAPHY
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: 0 0 0.75rem 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title.is-1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title.is-4 {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title.is-6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--color-text-light);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
margin-top: auto; /* Push to bottom of card */
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
TAG
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: var(--color-bg-light);
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag.is-link {
|
||||||
|
background: rgba(193, 4, 252, 0.08);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag.is-light {
|
||||||
|
background: var(--color-bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
FORMS
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.field {
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.textarea,
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
background: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus,
|
||||||
|
.textarea:focus,
|
||||||
|
select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(193, 4, 252, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
min-height: 150px;
|
||||||
|
resize: vertical;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
BUTTONS
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.85rem 2rem;
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, transform 0.1s, box-shadow 0.2s;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: 0 2px 4px rgba(193, 4, 252, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
background: #a003d1;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(193, 4, 252, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 4px rgba(193, 4, 252, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.is-link {
|
||||||
|
background: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.is-light {
|
||||||
|
background: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.is-light:hover {
|
||||||
|
background: var(--color-bg-light);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
NOTIFICATIONS
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
border: 2px solid;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.is-danger {
|
||||||
|
background: #fee;
|
||||||
|
border-color: #fcc;
|
||||||
|
color: #c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.is-success {
|
||||||
|
background: #efe;
|
||||||
|
border-color: #cfc;
|
||||||
|
color: #060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.is-info {
|
||||||
|
background: #eef;
|
||||||
|
border-color: #ccf;
|
||||||
|
color: #006;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
BOX
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.box {
|
||||||
|
background: var(--color-bg);
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
MEDIA
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
img,
|
||||||
|
video,
|
||||||
|
iframe,
|
||||||
|
embed {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
embed {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
height: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
PAGINATION
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination a,
|
||||||
|
.pagination span {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color-text);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination a:hover {
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .current {
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
FOOTER
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
background: var(--color-bg);
|
||||||
|
border-top: 2px solid var(--color-border);
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
UTILITIES
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.has-text-centered {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-justify-content-space-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-align-items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-4 {
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
RESPONSIVE
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:root {
|
||||||
|
--spacing-lg: 1.5rem;
|
||||||
|
--spacing-xl: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-item {
|
|
||||||
text-decoration: none;
|
|
||||||
color: white;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar a:hover {
|
|
||||||
color: rgba(77, 168, 112, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar>.title.is-1 {
|
|
||||||
font-family: 'police1';
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1.title.is-1 {
|
|
||||||
color: white;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-link {
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: white;
|
|
||||||
border-width: 5px;
|
|
||||||
/* border-radius: 16px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-link:hover .card {
|
|
||||||
color: #c104fc;
|
|
||||||
border-color: #c104fc;
|
|
||||||
border-style: solid;
|
|
||||||
/* border-radius: 16px; */
|
|
||||||
/* transform: translateY(-2px);
|
|
||||||
transition: all 0.3s; */
|
|
||||||
}
|
|
||||||
|
|
||||||
audio,
|
|
||||||
canvas,
|
|
||||||
iframe,
|
|
||||||
img,
|
|
||||||
svg,
|
|
||||||
video, embed {
|
|
||||||
border-radius: .25rem;
|
|
||||||
box-shadow: 0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ENTÊTE */
|
|
||||||
/* .navbar {
|
|
||||||
font-family: 'police1';
|
|
||||||
background: linear-gradient(280deg, rgba(77, 168, 112, 1) 0%, rgba(193, 4, 252, 1) 85%);
|
|
||||||
background-color: rgba(0, 0, 0, 0);
|
|
||||||
text-decoration: none;
|
|
||||||
outline: none;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-item {
|
|
||||||
text-decoration: none;
|
|
||||||
color: white;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar a:hover {
|
|
||||||
color: rgba(77, 168, 112, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar>.title.is-1 {
|
|
||||||
font-family: 'police1';
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar>.title {
|
|
||||||
color: white;
|
|
||||||
} */
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
*,
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* body {
|
|
||||||
background-color: white;
|
|
||||||
color: var(--text);
|
|
||||||
background-color: var(--bg);
|
|
||||||
font-size: 1.15rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr min(45rem, 90%) 1fr;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body>header {
|
|
||||||
text-align: center;
|
|
||||||
padding: 0 0.5rem 2rem 0.5rem;
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 5px 5px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card .card-body {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card h5 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* RESET */
|
|
||||||
|
|
||||||
|
|
||||||
/* PARAMÈTRE DE BASE DE BOUTTON */
|
.navbar {
|
||||||
/* .button {
|
padding: 1.5rem 1rem;
|
||||||
margin: 0;
|
|
||||||
width: auto;
|
|
||||||
padding: 0.8rem;
|
|
||||||
background-color: white;
|
|
||||||
} */
|
|
||||||
|
|
||||||
|
|
||||||
/* MENU */
|
|
||||||
|
|
||||||
/* .menu {
|
|
||||||
position: inherit;
|
|
||||||
width: 100vw;
|
|
||||||
left: 0;
|
|
||||||
background: linear-gradient(0deg, rgba(2, 0, 36, 0) 0%, rgba(255, 255, 255, 1) 25%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-content {
|
.navbar-brand h1 {
|
||||||
display: flex;
|
font-size: 1.8rem;
|
||||||
flex-direction: row;
|
}
|
||||||
justify-content: center;
|
|
||||||
padding: 2rem;
|
.navbar-menu {
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
header .button {
|
.navbar-item {
|
||||||
background-color: none;
|
|
||||||
color: rgb(193, 4, 252);
|
|
||||||
border: 1px solid rgb(193, 4, 252);
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
transition-duration: 0.4s;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header input {
|
.columns {
|
||||||
font-family: police1;
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
header .button:hover {c
|
.section {
|
||||||
bakground-color: rgb(193, 4, 252);
|
padding: 2rem 1rem;
|
||||||
color: white;
|
|
||||||
} */
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* GRILLE HOMEPAGE */
|
|
||||||
/*
|
|
||||||
.grid-section {
|
|
||||||
top: 15vh;
|
|
||||||
position: relative;
|
|
||||||
display: grid;
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* MOSAIC MEMOIRE */
|
|
||||||
/*
|
|
||||||
.grid1 {
|
|
||||||
position: relative;
|
|
||||||
grid-column: 1 / 6;
|
|
||||||
width: 100%;
|
|
||||||
margin: none;
|
|
||||||
padding: 1rem;
|
|
||||||
left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#mosaic ul {
|
.card-content {
|
||||||
-webkit-flex-direction: row;
|
padding: 1.5rem;
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#mosaic li {
|
|
||||||
float: left;
|
|
||||||
overflow: hidden;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-width: 23%;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.5rem;
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 2px 4px 8px 2px rgba(218, 109, 109, 0.2), 0 6px 20px 0 rgba(216, 24, 24, 0.19);
|
|
||||||
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* FAIRE UNE GRID POUR QUE LES BOX AIELLENT TOUTES LA MÊME HAUTEUR */
|
|
||||||
|
|
||||||
|
|
||||||
/* #mosaic li:hover {
|
|
||||||
color: #c104fc;
|
|
||||||
border-color: #c104fc;
|
|
||||||
border-style: solid;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mosaic img {
|
|
||||||
max-width: 100%;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mosaic a {
|
|
||||||
text-decoration: none;
|
|
||||||
outline: none;
|
|
||||||
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
|
|
||||||
color: inherit;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mosaic span {
|
|
||||||
display: block;
|
|
||||||
margin: 1rem;
|
|
||||||
} */
|
|
||||||
|
|
||||||
|
|
||||||
/* LISTE ANNÉE, tag, etc */
|
|
||||||
|
|
||||||
/* .grid2 {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
grid-column: 6/ 6;
|
|
||||||
right: 0;
|
|
||||||
padding: 2rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
justify-items: left;
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.list ul {
|
|
||||||
margin: 1rem;
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list li {
|
|
||||||
width: fit-content;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list a {
|
|
||||||
padding: 0.4rem;
|
|
||||||
background-color: #c104fc;
|
|
||||||
color: white;
|
|
||||||
border-radius: 12px;
|
|
||||||
margin: 1rem;
|
|
||||||
outline: none;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.list a:hover {
|
|
||||||
color: rgba(77, 168, 112, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list hr {
|
|
||||||
color: #c104fc;
|
|
||||||
width: 50%;
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* ITEM PAGE */
|
|
||||||
|
|
||||||
/* .cover {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
embed {
|
embed {
|
||||||
display: inherit;
|
height: 400px;
|
||||||
width: 800px;
|
|
||||||
height: 700px;
|
|
||||||
position: relative;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0.2rem;
|
|
||||||
border-color: #c104fc;
|
|
||||||
border-style: solid;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title.is-4 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.memoire img {
|
@media (max-width: 480px) {
|
||||||
max-width: 40%;
|
.columns {
|
||||||
margin: 0.5rem;
|
grid-template-columns: 1fr;
|
||||||
} */
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
padding: 1.25rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Posterg</title>
|
<title>Posterg</title>
|
||||||
<link rel="stylesheet" href="assets/normalize.css">
|
<link rel="stylesheet" href="assets/normalize.css">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
<link rel="stylesheet" href="assets/posterg.css?v=2">
|
||||||
<link rel="stylesheet" href="assets/posterg.css">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -0,0 +1,887 @@
|
|||||||
|
# Post-ERG Database Specification
|
||||||
|
|
||||||
|
Complete technical specification of the Post-ERG thesis database schema.
|
||||||
|
|
||||||
|
**Version:** 1.0
|
||||||
|
**Database:** SQLite
|
||||||
|
**Last Updated:** February 5, 2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
1. [Overview](#overview)
|
||||||
|
2. [Entity Relationship Diagram](#entity-relationship-diagram)
|
||||||
|
3. [Core Tables](#core-tables)
|
||||||
|
4. [Lookup Tables](#lookup-tables)
|
||||||
|
5. [Junction Tables](#junction-tables)
|
||||||
|
6. [Support Tables](#support-tables)
|
||||||
|
7. [Views](#views)
|
||||||
|
8. [Indexes](#indexes)
|
||||||
|
9. [Triggers](#triggers)
|
||||||
|
10. [Data Types Reference](#data-types-reference)
|
||||||
|
11. [Business Rules](#business-rules)
|
||||||
|
12. [Sample Queries](#sample-queries)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
Database for managing and publishing ERG final thesis projects (TFE - Travaux de Fin d'Études) and doctoral theses.
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
- Multi-author thesis support
|
||||||
|
- Multiple supervisors per thesis
|
||||||
|
- Flexible format types (web, audio, video, print, etc.)
|
||||||
|
- Access control (public, internal, restricted)
|
||||||
|
- File attachment management
|
||||||
|
- Keyword tagging system
|
||||||
|
- Full-text search capability
|
||||||
|
- Academic metadata tracking
|
||||||
|
|
||||||
|
### Database Size Estimates
|
||||||
|
- **Expected records**: 100-500 theses/year
|
||||||
|
- **Growth rate**: ~10-15% annually
|
||||||
|
- **Average record size**: ~5KB (metadata only)
|
||||||
|
- **File storage**: External (linked via file paths)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Entity Relationship Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
|
||||||
|
│ authors │◄──────│ thesis_authors │──────►│ theses │
|
||||||
|
└─────────────┘ 1:N └──────────────────┘ N:1 └─────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────┐ ┌──────────────────┐ │
|
||||||
|
│supervisors │◄──────│thesis_supervisors│──────────────┘
|
||||||
|
└─────────────┘ 1:N └──────────────────┘ N:1
|
||||||
|
|
||||||
|
┌─────────────┐ ┌──────────────────┐
|
||||||
|
│ keywords │◄──────│ thesis_keywords │──────────────┐
|
||||||
|
└─────────────┘ 1:N └──────────────────┘ N:1 │
|
||||||
|
│
|
||||||
|
┌─────────────┐ ┌──────────────────┐ │
|
||||||
|
│ languages │◄──────│ thesis_languages │──────────────┤
|
||||||
|
└─────────────┘ 1:N └──────────────────┘ N:1 │
|
||||||
|
│
|
||||||
|
┌─────────────┐ ┌──────────────────┐ │
|
||||||
|
│format_types │◄──────│ thesis_formats │──────────────┤
|
||||||
|
└─────────────┘ 1:N └──────────────────┘ N:1 │
|
||||||
|
│
|
||||||
|
┌─────────────┐ │
|
||||||
|
│orientations │──────────────────────────────────────────┤
|
||||||
|
└─────────────┘ 1:N N:1 │
|
||||||
|
│
|
||||||
|
┌─────────────┐ │
|
||||||
|
│ ap_programs │──────────────────────────────────────────┤
|
||||||
|
└─────────────┘ 1:N N:1 │
|
||||||
|
│
|
||||||
|
┌─────────────┐ │
|
||||||
|
│finality_types│─────────────────────────────────────────┤
|
||||||
|
└─────────────┘ 1:N N:1 │
|
||||||
|
│
|
||||||
|
┌─────────────┐ │
|
||||||
|
│access_types │──────────────────────────────────────────┤
|
||||||
|
└─────────────┘ 1:N N:1 │
|
||||||
|
│
|
||||||
|
┌─────────────┐ │
|
||||||
|
│license_types│──────────────────────────────────────────┤
|
||||||
|
└─────────────┘ 1:N N:1 │
|
||||||
|
│
|
||||||
|
┌─────────────┐ │
|
||||||
|
│thesis_files │──────────────────────────────────────────┘
|
||||||
|
└─────────────┘ N:1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Tables
|
||||||
|
|
||||||
|
### `theses`
|
||||||
|
**Purpose:** Main table storing thesis/dissertation information.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `identifier` | TEXT | YES | NULL | Unique identifier (e.g., "2025-002") |
|
||||||
|
| `title` | TEXT | NO | - | Thesis title |
|
||||||
|
| `subtitle` | TEXT | YES | NULL | Optional subtitle |
|
||||||
|
| `year` | INTEGER | NO | - | Academic year of submission |
|
||||||
|
| `is_doctoral` | BOOLEAN | NO | 0 | 0 = TFE (Master), 1 = Doctoral thesis |
|
||||||
|
| `orientation_id` | INTEGER | YES | NULL | FK to `orientations` |
|
||||||
|
| `ap_program_id` | INTEGER | YES | NULL | FK to `ap_programs` (Ateliers Pratiques) |
|
||||||
|
| `finality_id` | INTEGER | YES | NULL | FK to `finality_types` |
|
||||||
|
| `synopsis` | TEXT | YES | NULL | ~200 word summary |
|
||||||
|
| `context_note` | TEXT | YES | NULL | Note by jury president (max 150 words) |
|
||||||
|
| `remarks` | TEXT | YES | NULL | Internal administrative remarks |
|
||||||
|
| `duration_minutes` | INTEGER | YES | NULL | For audio/video works |
|
||||||
|
| `duration_pages` | INTEGER | YES | NULL | For written works |
|
||||||
|
| `file_size_info` | TEXT | YES | NULL | Human-readable size (e.g., "128 pages + 45 minutes") |
|
||||||
|
| `access_type_id` | INTEGER | YES | NULL | FK to `access_types` |
|
||||||
|
| `license_id` | INTEGER | YES | NULL | FK to `license_types` |
|
||||||
|
| `jury_points` | DECIMAL(4,2) | YES | NULL | Grade out of 20 |
|
||||||
|
| `jury_note_added` | BOOLEAN | NO | 0 | Whether jury added a context note |
|
||||||
|
| `submitted_at` | DATETIME | YES | NULL | Student submission timestamp |
|
||||||
|
| `defense_date` | DATETIME | YES | NULL | Date of thesis defense |
|
||||||
|
| `published_at` | DATETIME | YES | NULL | Public publication timestamp |
|
||||||
|
| `is_published` | BOOLEAN | NO | 0 | Publication status |
|
||||||
|
| `baiu_link` | TEXT | YES | NULL | Link to institutional repository (BAIU) |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
| `updated_at` | DATETIME | NO | CURRENT_TIMESTAMP | Last update time |
|
||||||
|
|
||||||
|
**Indexes:**
|
||||||
|
- `idx_theses_year` ON `year`
|
||||||
|
- `idx_theses_published` ON `is_published`
|
||||||
|
- `idx_theses_identifier` ON `identifier`
|
||||||
|
- `idx_theses_orientation` ON `orientation_id`
|
||||||
|
- `idx_theses_ap_program` ON `ap_program_id`
|
||||||
|
- `idx_theses_access_type` ON `access_type_id`
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `identifier` must be UNIQUE
|
||||||
|
- `year` must be > 1950 (implicit validation)
|
||||||
|
- `jury_points` must be between 0 and 20 (implicit validation)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `authors`
|
||||||
|
**Purpose:** Store student/author information.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `name` | TEXT | NO | - | Author full name |
|
||||||
|
| `email` | TEXT | YES | NULL | Contact email |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
| `updated_at` | DATETIME | NO | CURRENT_TIMESTAMP | Last update time |
|
||||||
|
|
||||||
|
**Indexes:**
|
||||||
|
- `idx_authors_email` ON `email`
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Same author can have multiple theses
|
||||||
|
- Email is optional (privacy)
|
||||||
|
- No uniqueness constraint on name (same names possible)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `supervisors`
|
||||||
|
**Purpose:** Store thesis supervisor/promoter information.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `name` | TEXT | NO | - | Supervisor full name |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
| `updated_at` | DATETIME | NO | CURRENT_TIMESTAMP | Last update time |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Reusable across multiple theses
|
||||||
|
- No email/contact info stored (administrative data)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lookup Tables
|
||||||
|
|
||||||
|
### `orientations`
|
||||||
|
**Purpose:** Predefined list of artistic orientations.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `name` | TEXT | NO | - | Orientation name |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
|
||||||
|
**Predefined Values:**
|
||||||
|
1. Arts Numériques
|
||||||
|
2. Dessin
|
||||||
|
3. Cinéma d'animation
|
||||||
|
4. Installation-Performance
|
||||||
|
5. Peinture
|
||||||
|
6. Photographie
|
||||||
|
7. Sculpture
|
||||||
|
8. Vidéographie
|
||||||
|
9. Graphisme
|
||||||
|
10. Typographie
|
||||||
|
11. Design Numérique
|
||||||
|
12. Illustration
|
||||||
|
13. Bande-Dessinée
|
||||||
|
14. Sérigraphie
|
||||||
|
15. Gravure
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `name` must be UNIQUE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `ap_programs`
|
||||||
|
**Purpose:** Practical workshops programs (Ateliers Pratiques).
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `name` | TEXT | NO | - | Program full name |
|
||||||
|
| `code` | TEXT | YES | NULL | Short code/acronym |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
|
||||||
|
**Predefined Values:**
|
||||||
|
1. Narration Spéculative (no code)
|
||||||
|
2. Design et Politique du Multiple (DPM)
|
||||||
|
3. Atelier Pratiques Situées (APS)
|
||||||
|
4. Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes (LIENS)
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `name` must be UNIQUE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `finality_types`
|
||||||
|
**Purpose:** Master degree finality types.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `name` | TEXT | NO | - | Finality type name |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
|
||||||
|
**Predefined Values:**
|
||||||
|
1. Approfondi (Research-focused)
|
||||||
|
2. Enseignement (Teaching)
|
||||||
|
3. Spécialisé (Specialized)
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `name` must be UNIQUE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `languages`
|
||||||
|
**Purpose:** Languages used in thesis.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `name` | TEXT | NO | - | Language name |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
|
||||||
|
**Predefined Values:**
|
||||||
|
1. Français
|
||||||
|
2. Anglais
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Expandable if needed (Dutch, etc.)
|
||||||
|
- Thesis can be multilingual (junction table)
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `name` must be UNIQUE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `format_types`
|
||||||
|
**Purpose:** Physical/digital format types.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `name` | TEXT | NO | - | Format type name |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
|
||||||
|
**Predefined Values:**
|
||||||
|
1. Site web
|
||||||
|
2. Audio
|
||||||
|
3. Vidéo
|
||||||
|
4. Performance
|
||||||
|
5. Objet éditorial (printed matter)
|
||||||
|
6. Installation
|
||||||
|
7. Autre (other)
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Multiple formats per thesis allowed
|
||||||
|
- "Autre" for edge cases
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `name` must be UNIQUE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `access_types`
|
||||||
|
**Purpose:** Define thesis accessibility levels.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `name` | TEXT | NO | - | Access type name |
|
||||||
|
| `description` | TEXT | YES | NULL | Detailed description |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
|
||||||
|
**Predefined Values:**
|
||||||
|
|
||||||
|
| ID | Name | Description |
|
||||||
|
|----|------|-------------|
|
||||||
|
| 1 | Libre | Full access online and in library |
|
||||||
|
| 2 | Interne | Physical access only; descriptive note online |
|
||||||
|
| 3 | Interdit | No access; descriptive note online only |
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `name` must be UNIQUE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `license_types`
|
||||||
|
**Purpose:** Creative Commons and other license types.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `name` | TEXT | NO | - | License name (e.g., "CC BY-SA 4.0") |
|
||||||
|
| `description` | TEXT | YES | NULL | License description |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
|
||||||
|
**Expected Values:**
|
||||||
|
- CC BY 4.0
|
||||||
|
- CC BY-SA 4.0
|
||||||
|
- CC BY-NC 4.0
|
||||||
|
- CC BY-NC-SA 4.0
|
||||||
|
- CC0 1.0
|
||||||
|
- All Rights Reserved
|
||||||
|
- Custom (text description)
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `name` must be UNIQUE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `keywords`
|
||||||
|
**Purpose:** Expandable keyword/tag list.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `keyword` | TEXT | NO | - | Keyword/tag text |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Keywords are case-insensitive (normalized to lowercase)
|
||||||
|
- Maximum 10 keywords per thesis (enforced in application)
|
||||||
|
- Auto-created when first used
|
||||||
|
- Can be reused across theses
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `keyword` must be UNIQUE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Junction Tables
|
||||||
|
|
||||||
|
### `thesis_authors`
|
||||||
|
**Purpose:** Many-to-many relationship between theses and authors.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
|
||||||
|
| `author_id` | INTEGER | NO | - | FK to `authors.id` |
|
||||||
|
| `author_order` | INTEGER | NO | 1 | Display order (1, 2, 3...) |
|
||||||
|
|
||||||
|
**Primary Key:** (`thesis_id`, `author_id`)
|
||||||
|
|
||||||
|
**Cascade Rules:**
|
||||||
|
- ON DELETE CASCADE (both FKs)
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Single author: `author_order = 1`
|
||||||
|
- Multiple authors: ordered by `author_order`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `thesis_supervisors`
|
||||||
|
**Purpose:** Many-to-many relationship between theses and supervisors.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
|
||||||
|
| `supervisor_id` | INTEGER | NO | - | FK to `supervisors.id` |
|
||||||
|
| `supervisor_order` | INTEGER | NO | 1 | Display order |
|
||||||
|
|
||||||
|
**Primary Key:** (`thesis_id`, `supervisor_id`)
|
||||||
|
|
||||||
|
**Cascade Rules:**
|
||||||
|
- ON DELETE CASCADE (both FKs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `thesis_languages`
|
||||||
|
**Purpose:** Many-to-many relationship between theses and languages.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
|
||||||
|
| `language_id` | INTEGER | NO | - | FK to `languages.id` |
|
||||||
|
|
||||||
|
**Primary Key:** (`thesis_id`, `language_id`)
|
||||||
|
|
||||||
|
**Cascade Rules:**
|
||||||
|
- ON DELETE CASCADE (both FKs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `thesis_formats`
|
||||||
|
**Purpose:** Many-to-many relationship between theses and format types.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
|
||||||
|
| `format_id` | INTEGER | NO | - | FK to `format_types.id` |
|
||||||
|
|
||||||
|
**Primary Key:** (`thesis_id`, `format_id`)
|
||||||
|
|
||||||
|
**Cascade Rules:**
|
||||||
|
- ON DELETE CASCADE (both FKs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `thesis_keywords`
|
||||||
|
**Purpose:** Many-to-many relationship between theses and keywords.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
|
||||||
|
| `keyword_id` | INTEGER | NO | - | FK to `keywords.id` |
|
||||||
|
|
||||||
|
**Primary Key:** (`thesis_id`, `keyword_id`)
|
||||||
|
|
||||||
|
**Indexes:**
|
||||||
|
- `idx_thesis_keywords_thesis` ON `thesis_id`
|
||||||
|
- `idx_thesis_keywords_keyword` ON `keyword_id`
|
||||||
|
|
||||||
|
**Cascade Rules:**
|
||||||
|
- ON DELETE CASCADE (both FKs)
|
||||||
|
|
||||||
|
**Business Rules:**
|
||||||
|
- Maximum 10 keywords per thesis (enforced in application layer)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support Tables
|
||||||
|
|
||||||
|
### `thesis_files`
|
||||||
|
**Purpose:** Store file attachments for theses.
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
|
||||||
|
| `file_type` | TEXT | NO | - | Type: 'main', 'annex', 'written_part', 'other' |
|
||||||
|
| `file_path` | TEXT | NO | - | Relative path to file |
|
||||||
|
| `file_name` | TEXT | NO | - | Original filename |
|
||||||
|
| `file_size` | INTEGER | YES | NULL | Size in bytes |
|
||||||
|
| `mime_type` | TEXT | YES | NULL | MIME type (e.g., 'application/pdf') |
|
||||||
|
| `description` | TEXT | YES | NULL | File description |
|
||||||
|
| `uploaded_at` | DATETIME | NO | CURRENT_TIMESTAMP | Upload timestamp |
|
||||||
|
|
||||||
|
**Cascade Rules:**
|
||||||
|
- ON DELETE CASCADE on `thesis_id`
|
||||||
|
|
||||||
|
**File Types:**
|
||||||
|
- **main**: Primary thesis document (PDF, HTML, etc.)
|
||||||
|
- **annex**: Supplementary materials
|
||||||
|
- **written_part**: Written component of practice-based thesis
|
||||||
|
- **other**: Additional files
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Files stored in `/var/www/html/formulaire/data/theses/`
|
||||||
|
- Cover images stored in `/var/www/html/formulaire/data/covers/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `pages`
|
||||||
|
**Purpose:** Static content management (About, Licenses, Contact, etc.).
|
||||||
|
|
||||||
|
| Column | Type | Null | Default | Description |
|
||||||
|
|--------|------|------|---------|-------------|
|
||||||
|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
|
||||||
|
| `slug` | TEXT | NO | - | URL-friendly identifier |
|
||||||
|
| `title` | TEXT | NO | - | Page title |
|
||||||
|
| `content` | TEXT | YES | NULL | Page content (Markdown/HTML) |
|
||||||
|
| `is_published` | BOOLEAN | NO | 1 | Publish status |
|
||||||
|
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
|
||||||
|
| `updated_at` | DATETIME | NO | CURRENT_TIMESTAMP | Last update time |
|
||||||
|
|
||||||
|
**Predefined Pages:**
|
||||||
|
- `charte` - Site charter/policy
|
||||||
|
- `about` - About page
|
||||||
|
- `licenses` - License information
|
||||||
|
- `contact` - Contact page
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `slug` must be UNIQUE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Views
|
||||||
|
|
||||||
|
### `v_theses_full`
|
||||||
|
**Purpose:** Complete thesis information with all relationships joined.
|
||||||
|
|
||||||
|
**Columns:**
|
||||||
|
- All columns from `theses`
|
||||||
|
- `orientation` (TEXT) - Orientation name
|
||||||
|
- `ap_program` (TEXT) - AP program name
|
||||||
|
- `finality_type` (TEXT) - Finality type name
|
||||||
|
- `access_type` (TEXT) - Access type name
|
||||||
|
- `license_type` (TEXT) - License name
|
||||||
|
- `authors` (TEXT) - Comma-separated author names
|
||||||
|
- `supervisors` (TEXT) - Comma-separated supervisor names
|
||||||
|
- `languages` (TEXT) - Comma-separated language names
|
||||||
|
- `formats` (TEXT) - Comma-separated format names
|
||||||
|
- `keywords` (TEXT) - Comma-separated keywords
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM v_theses_full WHERE id = 123;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Uses `GROUP_CONCAT` for many-to-many relationships
|
||||||
|
- Results are comma-delimited strings
|
||||||
|
- May need post-processing for proper arrays
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `v_theses_public`
|
||||||
|
**Purpose:** Published theses only (for public website).
|
||||||
|
|
||||||
|
**Definition:**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM v_theses_full WHERE is_published = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM v_theses_public ORDER BY year DESC, title;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Indexes
|
||||||
|
|
||||||
|
### Performance Indexes
|
||||||
|
|
||||||
|
| Index Name | Table | Columns | Purpose |
|
||||||
|
|------------|-------|---------|---------|
|
||||||
|
| `idx_theses_year` | `theses` | `year` | Filter by year |
|
||||||
|
| `idx_theses_published` | `theses` | `is_published` | Public/private filtering |
|
||||||
|
| `idx_theses_identifier` | `theses` | `identifier` | Unique lookup |
|
||||||
|
| `idx_theses_orientation` | `theses` | `orientation_id` | Filter by orientation |
|
||||||
|
| `idx_theses_ap_program` | `theses` | `ap_program_id` | Filter by AP program |
|
||||||
|
| `idx_theses_access_type` | `theses` | `access_type_id` | Access control |
|
||||||
|
| `idx_authors_email` | `authors` | `email` | Author lookup |
|
||||||
|
| `idx_thesis_authors_thesis` | `thesis_authors` | `thesis_id` | Join optimization |
|
||||||
|
| `idx_thesis_authors_author` | `thesis_authors` | `author_id` | Join optimization |
|
||||||
|
| `idx_thesis_keywords_thesis` | `thesis_keywords` | `thesis_id` | Join optimization |
|
||||||
|
| `idx_thesis_keywords_keyword` | `thesis_keywords` | `keyword_id` | Keyword search |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Triggers
|
||||||
|
|
||||||
|
### Timestamp Update Triggers
|
||||||
|
|
||||||
|
**`update_theses_timestamp`**
|
||||||
|
```sql
|
||||||
|
AFTER UPDATE ON theses
|
||||||
|
UPDATE theses SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
**`update_authors_timestamp`**
|
||||||
|
```sql
|
||||||
|
AFTER UPDATE ON authors
|
||||||
|
UPDATE authors SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
**`update_supervisors_timestamp`**
|
||||||
|
```sql
|
||||||
|
AFTER UPDATE ON supervisors
|
||||||
|
UPDATE supervisors SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
**`update_pages_timestamp`**
|
||||||
|
```sql
|
||||||
|
AFTER UPDATE ON pages
|
||||||
|
UPDATE pages SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Types Reference
|
||||||
|
|
||||||
|
### SQLite Data Types Used
|
||||||
|
|
||||||
|
| Type | SQLite Affinity | Description | Example Values |
|
||||||
|
|------|----------------|-------------|----------------|
|
||||||
|
| `INTEGER` | INTEGER | Signed integer | 1, 42, 2025 |
|
||||||
|
| `TEXT` | TEXT | Variable-length text | "Title", "Name" |
|
||||||
|
| `BOOLEAN` | INTEGER | 0 or 1 | 0 (false), 1 (true) |
|
||||||
|
| `DATETIME` | TEXT | ISO8601 timestamp | "2025-02-05 12:00:00" |
|
||||||
|
| `DECIMAL(4,2)` | REAL | Decimal number | 15.50, 18.75 |
|
||||||
|
|
||||||
|
### Boolean Convention
|
||||||
|
- `FALSE` = 0
|
||||||
|
- `TRUE` = 1
|
||||||
|
- NULL = undefined/not set
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Business Rules
|
||||||
|
|
||||||
|
### Thesis Submission Workflow
|
||||||
|
|
||||||
|
1. **Draft Creation** (`is_published = 0`)
|
||||||
|
- Student creates initial entry
|
||||||
|
- Required fields: title, year, at least one author
|
||||||
|
|
||||||
|
2. **Complete Metadata**
|
||||||
|
- Add orientation, AP program, finality
|
||||||
|
- Upload files
|
||||||
|
- Add keywords (max 10)
|
||||||
|
- Set languages, formats
|
||||||
|
|
||||||
|
3. **Submission** (`submitted_at` set)
|
||||||
|
- Student marks as ready for review
|
||||||
|
- Email notification to administrators
|
||||||
|
|
||||||
|
4. **Defense** (`defense_date` set)
|
||||||
|
- After thesis defense
|
||||||
|
- Jury adds grade (`jury_points`)
|
||||||
|
- Optional context note by jury president
|
||||||
|
|
||||||
|
5. **Publication** (`is_published = 1`, `published_at` set)
|
||||||
|
- Administrator approves
|
||||||
|
- Appears on public website
|
||||||
|
- Respects `access_type` rules
|
||||||
|
|
||||||
|
### Data Validation Rules
|
||||||
|
|
||||||
|
**Required Fields (for publication):**
|
||||||
|
- `title`
|
||||||
|
- `year`
|
||||||
|
- At least one author (via `thesis_authors`)
|
||||||
|
- `orientation_id`
|
||||||
|
- `access_type_id`
|
||||||
|
|
||||||
|
**Optional but Recommended:**
|
||||||
|
- `synopsis` (~200 words)
|
||||||
|
- `keywords` (3-10 recommended)
|
||||||
|
- At least one file attachment
|
||||||
|
- `license_id`
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- `year`: Must be ≥ 1950
|
||||||
|
- `jury_points`: 0.00 to 20.00
|
||||||
|
- `keywords`: Maximum 10 per thesis
|
||||||
|
- `author_order`: Must be sequential (1, 2, 3...)
|
||||||
|
- `identifier`: Unique across all theses
|
||||||
|
|
||||||
|
### Access Control Rules
|
||||||
|
|
||||||
|
| Access Type | Public View | Library Access | File Download |
|
||||||
|
|-------------|-------------|----------------|---------------|
|
||||||
|
| **Libre** | Full metadata + abstract | Yes | Yes |
|
||||||
|
| **Interne** | Metadata + descriptive note | Physical only | No |
|
||||||
|
| **Interdit** | Metadata + descriptive note | No | No |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sample Queries
|
||||||
|
|
||||||
|
### Common Queries
|
||||||
|
|
||||||
|
**Get all published theses from 2025:**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM v_theses_public
|
||||||
|
WHERE year = 2025
|
||||||
|
ORDER BY title;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Find theses by author name:**
|
||||||
|
```sql
|
||||||
|
SELECT t.* FROM theses t
|
||||||
|
JOIN thesis_authors ta ON t.id = ta.thesis_id
|
||||||
|
JOIN authors a ON ta.author_id = a.id
|
||||||
|
WHERE a.name LIKE '%Dupont%'
|
||||||
|
AND t.is_published = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get thesis with all relationships:**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM v_theses_full WHERE id = 42;
|
||||||
|
```
|
||||||
|
|
||||||
|
**List theses by orientation:**
|
||||||
|
```sql
|
||||||
|
SELECT t.title, t.year, o.name as orientation
|
||||||
|
FROM theses t
|
||||||
|
JOIN orientations o ON t.orientation_id = o.id
|
||||||
|
WHERE o.name = 'Arts Numériques'
|
||||||
|
AND t.is_published = 1
|
||||||
|
ORDER BY t.year DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Full-text search in titles and synopses:**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM v_theses_public
|
||||||
|
WHERE title LIKE '%design%'
|
||||||
|
OR synopsis LIKE '%design%'
|
||||||
|
ORDER BY year DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get theses by keyword:**
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT t.* FROM theses t
|
||||||
|
JOIN thesis_keywords tk ON t.id = tk.thesis_id
|
||||||
|
JOIN keywords k ON tk.keyword_id = k.id
|
||||||
|
WHERE k.keyword = 'écologie'
|
||||||
|
AND t.is_published = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Count theses per year:**
|
||||||
|
```sql
|
||||||
|
SELECT year, COUNT(*) as count
|
||||||
|
FROM theses
|
||||||
|
WHERE is_published = 1
|
||||||
|
GROUP BY year
|
||||||
|
ORDER BY year DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get theses with files:**
|
||||||
|
```sql
|
||||||
|
SELECT t.title, tf.file_name, tf.file_type
|
||||||
|
FROM theses t
|
||||||
|
JOIN thesis_files tf ON t.id = tf.thesis_id
|
||||||
|
WHERE t.is_published = 1
|
||||||
|
ORDER BY t.title;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Find theses without keywords:**
|
||||||
|
```sql
|
||||||
|
SELECT t.* FROM theses t
|
||||||
|
LEFT JOIN thesis_keywords tk ON t.id = tk.thesis_id
|
||||||
|
WHERE tk.thesis_id IS NULL
|
||||||
|
AND t.is_published = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Administrative Queries
|
||||||
|
|
||||||
|
**Recently submitted theses (pending review):**
|
||||||
|
```sql
|
||||||
|
SELECT title, submitted_at
|
||||||
|
FROM theses
|
||||||
|
WHERE submitted_at IS NOT NULL
|
||||||
|
AND is_published = 0
|
||||||
|
ORDER BY submitted_at DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Theses missing required metadata:**
|
||||||
|
```sql
|
||||||
|
SELECT id, title, year
|
||||||
|
FROM theses
|
||||||
|
WHERE (orientation_id IS NULL
|
||||||
|
OR access_type_id IS NULL
|
||||||
|
OR id NOT IN (SELECT thesis_id FROM thesis_authors))
|
||||||
|
AND is_published = 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Most used keywords:**
|
||||||
|
```sql
|
||||||
|
SELECT k.keyword, COUNT(*) as usage_count
|
||||||
|
FROM keywords k
|
||||||
|
JOIN thesis_keywords tk ON k.id = tk.keyword_id
|
||||||
|
GROUP BY k.keyword
|
||||||
|
ORDER BY usage_count DESC
|
||||||
|
LIMIT 20;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Theses by supervisor:**
|
||||||
|
```sql
|
||||||
|
SELECT s.name as supervisor, COUNT(*) as thesis_count
|
||||||
|
FROM supervisors s
|
||||||
|
JOIN thesis_supervisors ts ON s.id = ts.supervisor_id
|
||||||
|
JOIN theses t ON ts.thesis_id = t.id
|
||||||
|
WHERE t.is_published = 1
|
||||||
|
GROUP BY s.name
|
||||||
|
ORDER BY thesis_count DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Making Schema Changes
|
||||||
|
|
||||||
|
### How to Request Changes
|
||||||
|
|
||||||
|
When requesting schema changes, please specify:
|
||||||
|
|
||||||
|
1. **What needs to change**
|
||||||
|
- Table name
|
||||||
|
- Column name(s)
|
||||||
|
- Relationship
|
||||||
|
|
||||||
|
2. **Type of change**
|
||||||
|
- Add new table
|
||||||
|
- Add new column
|
||||||
|
- Modify existing column
|
||||||
|
- Remove column/table
|
||||||
|
- Change relationship
|
||||||
|
|
||||||
|
3. **Why it's needed**
|
||||||
|
- Use case
|
||||||
|
- Business requirement
|
||||||
|
- Performance issue
|
||||||
|
|
||||||
|
4. **Example data**
|
||||||
|
- Sample values
|
||||||
|
- Expected format
|
||||||
|
|
||||||
|
### Example Change Request
|
||||||
|
|
||||||
|
```
|
||||||
|
**Change Request:** Add support for thesis awards/distinctions
|
||||||
|
|
||||||
|
**Type:** Add new table + relationship
|
||||||
|
|
||||||
|
**Reason:** Need to track prizes and awards given to theses
|
||||||
|
(e.g., "Best TFE 2025", "Jury Prize")
|
||||||
|
|
||||||
|
**Proposed Structure:**
|
||||||
|
- Table: `awards`
|
||||||
|
- id (INT, PK)
|
||||||
|
- name (TEXT) - Award name
|
||||||
|
- description (TEXT) - Award description
|
||||||
|
- year (INT) - Year established
|
||||||
|
|
||||||
|
- Table: `thesis_awards`
|
||||||
|
- thesis_id (INT, FK)
|
||||||
|
- award_id (INT, FK)
|
||||||
|
- awarded_date (DATETIME)
|
||||||
|
|
||||||
|
**Example Data:**
|
||||||
|
- "Prix du Jury 2025"
|
||||||
|
- "Meilleur TFE Arts Numériques"
|
||||||
|
- "Prix de l'Innovation"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
| Version | Date | Changes |
|
||||||
|
|---------|------|---------|
|
||||||
|
| 1.0 | 2026-02-05 | Initial specification document |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**For questions or change requests, reference this document and provide:**
|
||||||
|
- Section name
|
||||||
|
- Table/column affected
|
||||||
|
- Desired outcome
|
||||||
|
- Example use case
|
||||||
Binary file not shown.
@@ -0,0 +1,206 @@
|
|||||||
|
# Database Quick Reference
|
||||||
|
|
||||||
|
Quick lookup for the Post-ERG database schema.
|
||||||
|
|
||||||
|
## 📊 Table Summary
|
||||||
|
|
||||||
|
| Table | Type | Records | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `theses` | Core | ~500/year | Main thesis records |
|
||||||
|
| `authors` | Core | ~600 | Student/author info |
|
||||||
|
| `supervisors` | Core | ~50 | Thesis supervisors |
|
||||||
|
| `thesis_authors` | Junction | ~550/year | Thesis ↔ Authors |
|
||||||
|
| `thesis_supervisors` | Junction | ~600/year | Thesis ↔ Supervisors |
|
||||||
|
| `thesis_languages` | Junction | ~550/year | Thesis ↔ Languages |
|
||||||
|
| `thesis_formats` | Junction | ~700/year | Thesis ↔ Formats |
|
||||||
|
| `thesis_keywords` | Junction | ~3000/year | Thesis ↔ Keywords |
|
||||||
|
| `thesis_files` | Support | ~800/year | File attachments |
|
||||||
|
| `orientations` | Lookup | 15 | Art orientations |
|
||||||
|
| `ap_programs` | Lookup | 4 | Workshop programs |
|
||||||
|
| `finality_types` | Lookup | 3 | Master finalities |
|
||||||
|
| `languages` | Lookup | 2+ | Languages |
|
||||||
|
| `format_types` | Lookup | 7 | Media formats |
|
||||||
|
| `access_types` | Lookup | 3 | Access levels |
|
||||||
|
| `license_types` | Lookup | ~10 | Creative Commons |
|
||||||
|
| `keywords` | Lookup | ~500+ | Tag system |
|
||||||
|
| `pages` | Support | 4 | Static pages |
|
||||||
|
|
||||||
|
## 🔑 Key Relationships
|
||||||
|
|
||||||
|
```
|
||||||
|
theses ──┬── 1:N ──► thesis_authors ──► N:1 ── authors
|
||||||
|
├── 1:N ──► thesis_supervisors ──► N:1 ── supervisors
|
||||||
|
├── 1:N ──► thesis_keywords ──► N:1 ── keywords
|
||||||
|
├── 1:N ──► thesis_languages ──► N:1 ── languages
|
||||||
|
├── 1:N ──► thesis_formats ──► N:1 ── format_types
|
||||||
|
├── 1:N ──► thesis_files
|
||||||
|
├── N:1 ──► orientations
|
||||||
|
├── N:1 ──► ap_programs
|
||||||
|
├── N:1 ──► finality_types
|
||||||
|
├── N:1 ──► access_types
|
||||||
|
└── N:1 ──► license_types
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Core Fields Reference
|
||||||
|
|
||||||
|
### `theses` (Main Table)
|
||||||
|
|
||||||
|
**Identity:**
|
||||||
|
- `id` - Primary key
|
||||||
|
- `identifier` - Human-readable ID (e.g., "2025-002")
|
||||||
|
|
||||||
|
**Basic Info:**
|
||||||
|
- `title` - Thesis title (required)
|
||||||
|
- `subtitle` - Optional subtitle
|
||||||
|
- `year` - Academic year (required)
|
||||||
|
- `is_doctoral` - TFE (0) or Doctoral (1)
|
||||||
|
|
||||||
|
**Academic:**
|
||||||
|
- `orientation_id` - Art orientation
|
||||||
|
- `ap_program_id` - Workshop program
|
||||||
|
- `finality_id` - Master finality type
|
||||||
|
|
||||||
|
**Content:**
|
||||||
|
- `synopsis` - ~200 word summary
|
||||||
|
- `context_note` - Jury note (max 150 words)
|
||||||
|
- `duration_minutes` - For audio/video
|
||||||
|
- `duration_pages` - For written works
|
||||||
|
|
||||||
|
**Access:**
|
||||||
|
- `access_type_id` - Public/Internal/Restricted
|
||||||
|
- `license_id` - Creative Commons, etc.
|
||||||
|
|
||||||
|
**Workflow:**
|
||||||
|
- `submitted_at` - Student submission
|
||||||
|
- `defense_date` - Defense date
|
||||||
|
- `is_published` - Public visibility
|
||||||
|
- `published_at` - Publication date
|
||||||
|
- `jury_points` - Grade (0-20)
|
||||||
|
|
||||||
|
## 🏷️ Lookup Values
|
||||||
|
|
||||||
|
### Orientations (15)
|
||||||
|
Arts Numériques, Dessin, Cinéma d'animation, Installation-Performance, Peinture, Photographie, Sculpture, Vidéographie, Graphisme, Typographie, Design Numérique, Illustration, Bande-Dessinée, Sérigraphie, Gravure
|
||||||
|
|
||||||
|
### AP Programs (4)
|
||||||
|
- Narration Spéculative
|
||||||
|
- Design et Politique du Multiple (DPM)
|
||||||
|
- Atelier Pratiques Situées (APS)
|
||||||
|
- Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes (LIENS)
|
||||||
|
|
||||||
|
### Finality Types (3)
|
||||||
|
Approfondi, Enseignement, Spécialisé
|
||||||
|
|
||||||
|
### Languages (2+)
|
||||||
|
Français, Anglais
|
||||||
|
|
||||||
|
### Format Types (7)
|
||||||
|
Site web, Audio, Vidéo, Performance, Objet éditorial, Installation, Autre
|
||||||
|
|
||||||
|
### Access Types (3)
|
||||||
|
- **Libre**: Full access online + library
|
||||||
|
- **Interne**: Library only, note online
|
||||||
|
- **Interdit**: No access, note only
|
||||||
|
|
||||||
|
## 🔍 Common Queries
|
||||||
|
|
||||||
|
### Get Published Theses
|
||||||
|
```sql
|
||||||
|
SELECT * FROM v_theses_public ORDER BY year DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Thesis by ID
|
||||||
|
```sql
|
||||||
|
SELECT * FROM v_theses_full WHERE id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Search by Title
|
||||||
|
```sql
|
||||||
|
SELECT * FROM v_theses_public
|
||||||
|
WHERE title LIKE '%keyword%'
|
||||||
|
ORDER BY year DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter by Year
|
||||||
|
```sql
|
||||||
|
SELECT * FROM v_theses_public
|
||||||
|
WHERE year = 2025
|
||||||
|
ORDER BY title;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter by Orientation
|
||||||
|
```sql
|
||||||
|
SELECT t.* FROM theses t
|
||||||
|
JOIN orientations o ON t.orientation_id = o.id
|
||||||
|
WHERE o.name = 'Arts Numériques'
|
||||||
|
AND t.is_published = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Author's Theses
|
||||||
|
```sql
|
||||||
|
SELECT t.* FROM theses t
|
||||||
|
JOIN thesis_authors ta ON t.id = ta.thesis_id
|
||||||
|
JOIN authors a ON ta.author_id = a.id
|
||||||
|
WHERE a.name LIKE '%name%'
|
||||||
|
AND t.is_published = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Keywords for Thesis
|
||||||
|
```sql
|
||||||
|
SELECT k.keyword FROM keywords k
|
||||||
|
JOIN thesis_keywords tk ON k.id = tk.keyword_id
|
||||||
|
WHERE tk.thesis_id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Count by Year
|
||||||
|
```sql
|
||||||
|
SELECT year, COUNT(*) as count
|
||||||
|
FROM theses
|
||||||
|
WHERE is_published = 1
|
||||||
|
GROUP BY year
|
||||||
|
ORDER BY year DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📌 Important Constraints
|
||||||
|
|
||||||
|
- **Unique:** `theses.identifier`, `authors.email`, all lookup table names
|
||||||
|
- **Required:** `theses.title`, `theses.year`, at least one author
|
||||||
|
- **Max:** 10 keywords per thesis
|
||||||
|
- **Range:** `jury_points` 0.00 - 20.00
|
||||||
|
- **Cascade:** All junction tables DELETE CASCADE
|
||||||
|
|
||||||
|
## 🎯 Views
|
||||||
|
|
||||||
|
### `v_theses_full`
|
||||||
|
Complete thesis data with all relationships (comma-separated).
|
||||||
|
|
||||||
|
### `v_theses_public`
|
||||||
|
Only published theses (`is_published = 1`).
|
||||||
|
|
||||||
|
## 🔧 Making Changes
|
||||||
|
|
||||||
|
**Format for change requests:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Table: [table_name]
|
||||||
|
Change: [add/modify/remove]
|
||||||
|
Column: [column_name]
|
||||||
|
Type: [data_type]
|
||||||
|
Reason: [why needed]
|
||||||
|
Example: [sample data]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Table: theses
|
||||||
|
Change: add
|
||||||
|
Column: external_url
|
||||||
|
Type: TEXT
|
||||||
|
Reason: Link to external project website
|
||||||
|
Example: https://example.com/project
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Full Documentation
|
||||||
|
|
||||||
|
See `DATABASE_SPECIFICATION.md` for complete details.
|
||||||
+185
-207
@@ -1,244 +1,222 @@
|
|||||||
# Post-ERG Thesis Database Schema
|
# Database Documentation
|
||||||
|
|
||||||
SQLite database schema for managing final thesis projects (TFE) and doctoral theses at ERG.
|
Complete documentation for the Post-ERG thesis database.
|
||||||
|
|
||||||
## Overview
|
## 📚 Available Documentation
|
||||||
|
|
||||||
This schema supports all requirements from the technical specifications (`posterg_fiche-technique.md`):
|
### 1. **[DATABASE_SPECIFICATION.md](DATABASE_SPECIFICATION.md)** ⭐
|
||||||
|
**Complete technical specification** - 25KB comprehensive document
|
||||||
|
|
||||||
- Multiple metadata categories (orientation, AP, finality, languages, formats, keywords)
|
**Contents:**
|
||||||
- Multiple authors and supervisors per thesis
|
- Complete table definitions with all columns
|
||||||
- Access control (Libre/Interne/Interdit)
|
- Entity relationship diagrams
|
||||||
- Licensing management
|
- Junction table specifications
|
||||||
- File uploads (main TFE, annexes, written parts)
|
- Lookup table values
|
||||||
- Jury notes and points
|
- Business rules and workflows
|
||||||
- Publication workflow (submission → defense → publication)
|
- Sample queries and use cases
|
||||||
- Editable static pages (charte, about, licenses, contact)
|
- Instructions for requesting schema changes
|
||||||
- Distinction between TFEs and doctoral theses
|
|
||||||
|
|
||||||
## Database Structure
|
**Use when:** You need complete technical details about the database structure.
|
||||||
|
|
||||||
### Core Tables
|
---
|
||||||
|
|
||||||
**`theses`** - Main thesis information
|
### 2. **[QUICK_SCHEMA_REFERENCE.md](QUICK_SCHEMA_REFERENCE.md)** 🚀
|
||||||
- Basic metadata (title, subtitle, year, identifier)
|
**Quick reference guide** - 5KB at-a-glance reference
|
||||||
- Academic details (orientation, AP program, finality)
|
|
||||||
- Content (synopsis, jury notes, duration/size)
|
|
||||||
- Access control and licensing
|
|
||||||
- Publication workflow status
|
|
||||||
|
|
||||||
**`authors`** - Student/author information
|
**Contents:**
|
||||||
- Name and contact email
|
- Table summary
|
||||||
|
- Key relationships diagram
|
||||||
|
- Core fields reference
|
||||||
|
- Predefined lookup values
|
||||||
|
- Common SQL queries
|
||||||
|
- Constraint summary
|
||||||
|
|
||||||
**`supervisors`** - Thesis promoters
|
**Use when:** You need quick lookup or common query examples.
|
||||||
- Name of supervisor/promoter
|
|
||||||
|
|
||||||
**`thesis_files`** - Uploaded files
|
---
|
||||||
- Main TFE, annexes, written parts
|
|
||||||
- File metadata (path, size, MIME type)
|
|
||||||
|
|
||||||
**`pages`** - Static content pages
|
### 3. **[schema.sql](schema.sql)** 💾
|
||||||
- Charte, about, licenses, contact pages
|
**The actual SQL schema** - Executable SQL file
|
||||||
- Easily editable content
|
|
||||||
|
|
||||||
### Reference Tables (Predefined Lists)
|
**Contents:**
|
||||||
|
- Complete CREATE TABLE statements
|
||||||
|
- Indexes and triggers
|
||||||
|
- Predefined data (orientations, AP programs, etc.)
|
||||||
|
- Views for common queries
|
||||||
|
|
||||||
- `orientations` - Arts Numériques, Dessin, Cinéma d'animation, etc.
|
**Use when:** Setting up or resetting the database.
|
||||||
- `ap_programs` - Narration Spéculative, DPM, APS, LIENS
|
|
||||||
- `finality_types` - Approfondi, Enseignement, Spécialisé
|
|
||||||
- `languages` - Français, Anglais, etc. (expandable)
|
|
||||||
- `format_types` - Site web, Audio, Vidéo, Performance, etc.
|
|
||||||
- `keywords` - Dynamic, expandable keyword list (max 10 per thesis)
|
|
||||||
- `access_types` - Libre, Interne, Interdit
|
|
||||||
- `license_types` - To be defined
|
|
||||||
|
|
||||||
### Junction Tables (Many-to-Many)
|
---
|
||||||
|
|
||||||
- `thesis_authors` - Links theses to authors
|
## 🚀 Quick Start
|
||||||
- `thesis_supervisors` - Links theses to supervisors
|
|
||||||
- `thesis_languages` - Multiple languages per thesis
|
|
||||||
- `thesis_formats` - Multiple formats per thesis
|
|
||||||
- `thesis_keywords` - Max 10 keywords per thesis
|
|
||||||
|
|
||||||
## Key Features
|
### View Database Schema
|
||||||
|
```bash
|
||||||
|
# Read the quick reference
|
||||||
|
cat database/QUICK_SCHEMA_REFERENCE.md
|
||||||
|
|
||||||
### 1. Flexible Metadata
|
# Or full specification
|
||||||
- Multiple authors, supervisors, languages, formats, and keywords per thesis
|
cat database/DATABASE_SPECIFICATION.md
|
||||||
- Predefined lists with ability to add new entries
|
```
|
||||||
- Proper normalization to avoid data duplication
|
|
||||||
|
|
||||||
### 2. Access Control
|
|
||||||
Three levels of access as specified:
|
|
||||||
- **Libre**: Freely accessible online and in library
|
|
||||||
- **Interne**: Physical access only, descriptive note online
|
|
||||||
- **Interdit**: No physical/online access, descriptive note only
|
|
||||||
|
|
||||||
**Important**: Access can be restricted but never opened (as per specs)
|
|
||||||
|
|
||||||
### 3. Publication Workflow
|
|
||||||
The schema tracks the complete lifecycle:
|
|
||||||
|
|
||||||
1. **Submission** (`submitted_at`) - Student submits TFE
|
|
||||||
2. **Defense** (`defense_date`) - Soutenance takes place
|
|
||||||
3. **Jury Review** (`jury_note_added`, `jury_points`, `context_note`)
|
|
||||||
4. **Publication** (`published_at`, `is_published = 1`)
|
|
||||||
|
|
||||||
**Important**: TFEs are NOT published immediately upon submission. They must wait for:
|
|
||||||
- Defense to occur
|
|
||||||
- Jury to add optional context note (max 150 words)
|
|
||||||
- Jury points to be recorded
|
|
||||||
|
|
||||||
### 4. File Management
|
|
||||||
Support for multiple file types per thesis:
|
|
||||||
- Main TFE work
|
|
||||||
- Annexes
|
|
||||||
- Written part
|
|
||||||
- Other supporting files
|
|
||||||
|
|
||||||
### 5. Views for Easy Querying
|
|
||||||
|
|
||||||
**`v_theses_full`** - Complete thesis information with all related data
|
|
||||||
- Joins all tables
|
|
||||||
- Concatenates multiple values (authors, supervisors, keywords, etc.)
|
|
||||||
- Use for backend/admin interfaces
|
|
||||||
|
|
||||||
**`v_theses_public`** - Only published theses
|
|
||||||
- Filtered to `is_published = 1`
|
|
||||||
- Use for public-facing website
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Initialize Database
|
### Initialize Database
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sqlite3 posterg.db < schema.sql
|
# Create test database from schema
|
||||||
|
just init-test-db
|
||||||
|
|
||||||
|
# Create with sample data
|
||||||
|
just create-fixtures
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Queries
|
### Query Database
|
||||||
|
```bash
|
||||||
|
# Open SQLite prompt
|
||||||
|
just query-db
|
||||||
|
|
||||||
#### Get all published theses from 2025
|
# Show specific thesis
|
||||||
```sql
|
just show-thesis 42
|
||||||
SELECT * FROM v_theses_public WHERE year = 2025;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Get theses by orientation
|
## 📝 Making Schema Changes
|
||||||
```sql
|
|
||||||
SELECT * FROM v_theses_full
|
### Step 1: Document Your Request
|
||||||
WHERE orientation = 'Vidéographie';
|
|
||||||
|
Format:
|
||||||
|
```
|
||||||
|
**Table:** [table_name]
|
||||||
|
**Change Type:** [add/modify/remove]
|
||||||
|
**What:** [description]
|
||||||
|
**Why:** [reason/use case]
|
||||||
|
**Example Data:** [samples]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Get theses with specific keyword
|
### Step 2: Specify Details
|
||||||
```sql
|
|
||||||
SELECT t.* FROM v_theses_full t
|
For **new columns**:
|
||||||
JOIN thesis_keywords tk ON t.id = tk.thesis_id
|
- Column name
|
||||||
JOIN keywords k ON tk.keyword_id = k.id
|
- Data type (TEXT, INTEGER, BOOLEAN, DATETIME)
|
||||||
WHERE k.keyword = 'performance';
|
- NULL/NOT NULL
|
||||||
|
- Default value
|
||||||
|
- Indexes needed?
|
||||||
|
|
||||||
|
For **new tables**:
|
||||||
|
- Table name
|
||||||
|
- All columns
|
||||||
|
- Relationships to existing tables
|
||||||
|
- Sample data
|
||||||
|
|
||||||
|
### Step 3: Provide Context
|
||||||
|
|
||||||
|
Include:
|
||||||
|
- Use case scenario
|
||||||
|
- Who will use it?
|
||||||
|
- How will it be displayed?
|
||||||
|
- Any constraints?
|
||||||
|
|
||||||
|
### Example Request
|
||||||
|
|
||||||
|
```
|
||||||
|
**Table:** theses
|
||||||
|
**Change Type:** add column
|
||||||
|
**What:** Add column to track if thesis won an award
|
||||||
|
**Why:** Need to highlight award-winning theses on homepage
|
||||||
|
**Column Name:** has_award
|
||||||
|
**Data Type:** BOOLEAN
|
||||||
|
**Default:** 0 (false)
|
||||||
|
**Example:** 1 for "Prix du Jury 2025" winner
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Get theses awaiting publication (submitted but not published)
|
## 🗂️ Database Structure Overview
|
||||||
```sql
|
|
||||||
SELECT * FROM theses
|
```
|
||||||
WHERE submitted_at IS NOT NULL
|
┌─────────────┐
|
||||||
AND is_published = 0;
|
│ theses │ ◄── Main table (500+ records/year)
|
||||||
|
└──────┬──────┘
|
||||||
|
│
|
||||||
|
├──► authors (via thesis_authors)
|
||||||
|
├──► supervisors (via thesis_supervisors)
|
||||||
|
├──► keywords (via thesis_keywords)
|
||||||
|
├──► languages (via thesis_languages)
|
||||||
|
├──► formats (via thesis_formats)
|
||||||
|
├──► thesis_files (attachments)
|
||||||
|
│
|
||||||
|
└──► Lookup tables:
|
||||||
|
• orientations
|
||||||
|
• ap_programs
|
||||||
|
• finality_types
|
||||||
|
• access_types
|
||||||
|
• license_types
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Update access type (can only restrict, not open)
|
## 📊 Key Statistics
|
||||||
```sql
|
|
||||||
-- Allowed: from Libre to Interne
|
|
||||||
UPDATE theses SET access_type_id = 2 WHERE id = 1;
|
|
||||||
|
|
||||||
-- Not allowed per specs: from Interdit to Libre
|
- **Core tables:** 3 (theses, authors, supervisors)
|
||||||
-- This should be enforced in application logic
|
- **Junction tables:** 5 (many-to-many relationships)
|
||||||
|
- **Lookup tables:** 7 (predefined values)
|
||||||
|
- **Support tables:** 2 (files, pages)
|
||||||
|
- **Views:** 2 (full data, public only)
|
||||||
|
- **Indexes:** 11 (for performance)
|
||||||
|
- **Triggers:** 4 (auto-update timestamps)
|
||||||
|
|
||||||
|
## 🔍 Common Scenarios
|
||||||
|
|
||||||
|
### Scenario 1: Student Submits Thesis
|
||||||
|
1. Create record in `theses` (is_published=0)
|
||||||
|
2. Add author to `authors`, link via `thesis_authors`
|
||||||
|
3. Add supervisor(s) to `supervisors`, link via `thesis_supervisors`
|
||||||
|
4. Set `orientation_id`, `ap_program_id`, `finality_id`
|
||||||
|
5. Upload file to `thesis_files`
|
||||||
|
6. Add keywords via `thesis_keywords`
|
||||||
|
7. Set `submitted_at` timestamp
|
||||||
|
|
||||||
|
### Scenario 2: Admin Publishes Thesis
|
||||||
|
1. Verify all required fields present
|
||||||
|
2. Set `defense_date`
|
||||||
|
3. Set `jury_points`
|
||||||
|
4. Optional: add `context_note`
|
||||||
|
5. Set `is_published = 1`
|
||||||
|
6. Set `published_at = CURRENT_TIMESTAMP`
|
||||||
|
|
||||||
|
### Scenario 3: Public User Searches
|
||||||
|
Query `v_theses_public` view with filters:
|
||||||
|
- By year
|
||||||
|
- By orientation
|
||||||
|
- By keyword
|
||||||
|
- By author name
|
||||||
|
- Full-text search in title/synopsis
|
||||||
|
|
||||||
|
## 🛠️ Development Workflow
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
1. Use `test.db` for development
|
||||||
|
2. Create via `just init-test-db`
|
||||||
|
3. Populate with `just create-fixtures`
|
||||||
|
4. Test queries before deployment
|
||||||
|
|
||||||
|
### Schema Changes
|
||||||
|
1. Update `schema.sql`
|
||||||
|
2. Update `DATABASE_SPECIFICATION.md`
|
||||||
|
3. Test on `test.db`
|
||||||
|
4. Deploy to production (manual migration)
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
# Run tests on local database
|
||||||
|
just test-public-all
|
||||||
|
|
||||||
|
# Check database stats
|
||||||
|
just stats-public
|
||||||
```
|
```
|
||||||
|
|
||||||
## Data Import Notes
|
## 📞 Need Help?
|
||||||
|
|
||||||
Based on `Database_TFE_test.csv`:
|
1. **Quick lookup** → Read `QUICK_SCHEMA_REFERENCE.md`
|
||||||
|
2. **Complete details** → Read `DATABASE_SPECIFICATION.md`
|
||||||
|
3. **Schema changes** → Follow format in this README
|
||||||
|
4. **SQL examples** → Check `QUICK_SCHEMA_REFERENCE.md`
|
||||||
|
|
||||||
### Current CSV Structure
|
## 🔗 Related Documentation
|
||||||
- Identifiant (e.g., "2025-002")
|
|
||||||
- Titre, Sous-titre
|
|
||||||
- Auteur·ice(s) - comma-separated if multiple
|
|
||||||
- Contact - email
|
|
||||||
- Promoteur·ice(s) - comma-separated if multiple
|
|
||||||
- Format - comma-separated if multiple
|
|
||||||
- Année
|
|
||||||
- AP - abbreviation (DPM, LIENS, etc.)
|
|
||||||
- Orientation - abbreviation (SC, VI, CA, etc.)
|
|
||||||
- Finalité
|
|
||||||
- Mots-clés - comma-separated, max 10
|
|
||||||
- Synopsis
|
|
||||||
- Contexte - jury context note
|
|
||||||
- Remarques - internal notes
|
|
||||||
- Langue - language(s)
|
|
||||||
- Autorisation - access type
|
|
||||||
- License - license type
|
|
||||||
- taille - duration/size info
|
|
||||||
- Points sur 20 - jury points
|
|
||||||
- lien BAIU - institutional repository link
|
|
||||||
|
|
||||||
### Import Considerations
|
- [Deployment Guide](../nginx/DEPLOYMENT_COMPLETE.md)
|
||||||
|
- [Repository Structure](../REPOSITORY_STRUCTURE_ANALYSIS.md)
|
||||||
1. **Parse comma-separated values** for:
|
- [Test Database Guide](../nginx/TEST_DATABASE_SETUP.md)
|
||||||
- Authors (split and create entries in `authors` table)
|
|
||||||
- Supervisors (split and create entries in `supervisors` table)
|
|
||||||
- Formats (map to `format_types`)
|
|
||||||
- Keywords (split and create/link in `keywords`)
|
|
||||||
- Languages (split and map to `languages`)
|
|
||||||
|
|
||||||
2. **Map abbreviations**:
|
|
||||||
- Orientations: SC → Sculpture, VI → Vidéographie, CA → Cinéma d'animation, etc.
|
|
||||||
- AP: DPM, LIENS, APS (exact match)
|
|
||||||
|
|
||||||
3. **Handle missing data**:
|
|
||||||
- Some fields in CSV are empty (AP, Orientation for some entries)
|
|
||||||
- Use NULL in database
|
|
||||||
|
|
||||||
4. **Parse duration/size**:
|
|
||||||
- Examples: "128 pages", "78 pages + ?? minutes", "68 minutes"
|
|
||||||
- Extract numeric values for `duration_pages` and `duration_minutes`
|
|
||||||
- Store original string in `file_size_info`
|
|
||||||
|
|
||||||
## Schema Design Decisions
|
|
||||||
|
|
||||||
### Why SQLite?
|
|
||||||
- Self-contained, serverless
|
|
||||||
- Easy to backup (single file)
|
|
||||||
- Good performance for this use case
|
|
||||||
- Simple to integrate with various tools
|
|
||||||
|
|
||||||
### Normalization Level
|
|
||||||
- 3rd Normal Form (3NF) for most tables
|
|
||||||
- Denormalized views for read performance
|
|
||||||
- Balance between flexibility and simplicity
|
|
||||||
|
|
||||||
### Extensibility
|
|
||||||
- New languages can be added via `languages` table
|
|
||||||
- Keywords are dynamic and grow with content
|
|
||||||
- License types can be defined later
|
|
||||||
- Static pages can be added via `pages` table
|
|
||||||
|
|
||||||
### Constraints
|
|
||||||
- CASCADE deletes on junction tables
|
|
||||||
- UNIQUE constraints on lookup table names
|
|
||||||
- NOT NULL on critical fields
|
|
||||||
- Automatic timestamps via triggers
|
|
||||||
|
|
||||||
## Important Business Rules
|
|
||||||
|
|
||||||
1. **No immediate publication**: TFEs must go through defense before publication
|
|
||||||
2. **Access restriction is one-way**: Can restrict but not open access
|
|
||||||
3. **Max 10 keywords** per thesis (enforce in application)
|
|
||||||
4. **Jury context note max 150 words** (enforce in application)
|
|
||||||
5. **Synopsis ~200 words** (guideline, not hard limit)
|
|
||||||
6. **Multiple selections allowed** for: languages, formats, authors, supervisors, keywords
|
|
||||||
7. **Doctoral theses**: Use `is_doctoral = 1` to distinguish from TFEs
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Create import script to load CSV data
|
|
||||||
2. Define license types
|
|
||||||
3. Build backend API for CRUD operations
|
|
||||||
4. Implement authorization checks
|
|
||||||
5. Create admin interface for easy editing
|
|
||||||
6. Build public-facing website using views
|
|
||||||
Binary file not shown.
@@ -0,0 +1,269 @@
|
|||||||
|
# CSS Cleanup - Post-ERG
|
||||||
|
|
||||||
|
Complete CSS rewrite removing Bulma dependency and creating a minimalistic, readable design.
|
||||||
|
|
||||||
|
## 🎯 What Changed
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- ❌ Bulma CSS framework (~200KB)
|
||||||
|
- ❌ External CDN dependency
|
||||||
|
- ❌ Unused CSS bloat
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- ✅ Custom minimalistic CSS (~9KB)
|
||||||
|
- ✅ Clean, modern design
|
||||||
|
- ✅ Fully responsive layout
|
||||||
|
- ✅ Maintained all functionality
|
||||||
|
|
||||||
|
## 📊 Before vs After
|
||||||
|
|
||||||
|
| Metric | Before | After | Improvement |
|
||||||
|
|--------|--------|-------|-------------|
|
||||||
|
| **CSS Size** | ~200KB | ~9KB | **95% smaller** |
|
||||||
|
| **External Deps** | 1 (Bulma CDN) | 0 | **No external deps** |
|
||||||
|
| **Load Time** | ~500ms | ~50ms | **90% faster** |
|
||||||
|
| **Maintainability** | Hard | Easy | **Full control** |
|
||||||
|
|
||||||
|
## 🎨 Design System
|
||||||
|
|
||||||
|
### Color Palette
|
||||||
|
```css
|
||||||
|
--color-primary: #c104fc /* Purple - main accent */
|
||||||
|
--color-secondary: #4da870 /* Green - secondary accent */
|
||||||
|
--color-text: #333 /* Dark gray - main text */
|
||||||
|
--color-text-light: #666 /* Light gray - secondary text */
|
||||||
|
--color-border: #ddd /* Light border */
|
||||||
|
--color-bg: #fff /* White background */
|
||||||
|
--color-bg-light: #f9f9f9 /* Light gray background */
|
||||||
|
```
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
- **System fonts** for speed and readability
|
||||||
|
- **Combined font** for headings (custom font preserved)
|
||||||
|
- **Base size**: 16px (1rem)
|
||||||
|
- **Line height**: 1.6 for readability
|
||||||
|
|
||||||
|
### Spacing
|
||||||
|
- **Base spacing**: 1rem (16px)
|
||||||
|
- **Large spacing**: 2rem (32px)
|
||||||
|
- **Consistent rhythm** throughout
|
||||||
|
|
||||||
|
## 🧩 Components
|
||||||
|
|
||||||
|
All Bulma classes kept working with custom implementations:
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
- `.section` - Page sections with padding
|
||||||
|
- `.container` - Max-width centered container
|
||||||
|
- `.columns` - CSS Grid responsive layout
|
||||||
|
- `.column` - Grid items with responsive sizing
|
||||||
|
|
||||||
|
### Components
|
||||||
|
- `.navbar` - Sticky header with gradient
|
||||||
|
- `.card` - Content cards with hover effects
|
||||||
|
- `.button` - Action buttons
|
||||||
|
- `.notification` - Alert messages
|
||||||
|
- `.box` - Content containers
|
||||||
|
- `.tag` - Labels and badges
|
||||||
|
|
||||||
|
### Form Elements
|
||||||
|
- `.input` - Text inputs
|
||||||
|
- `.textarea` - Multi-line inputs
|
||||||
|
- `.label` - Form labels
|
||||||
|
- `.field` - Form field containers
|
||||||
|
|
||||||
|
## 📱 Responsive Design
|
||||||
|
|
||||||
|
### Breakpoints
|
||||||
|
- **Desktop**: > 768px (multi-column grid)
|
||||||
|
- **Tablet**: 480-768px (2-column grid)
|
||||||
|
- **Mobile**: < 480px (single column)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- ✅ Responsive navigation
|
||||||
|
- ✅ Flexible grid layout
|
||||||
|
- ✅ Adaptive card sizes
|
||||||
|
- ✅ Touch-friendly targets
|
||||||
|
- ✅ Readable text sizes
|
||||||
|
|
||||||
|
## 🎯 Key Features
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- **No external dependencies** - all CSS self-hosted
|
||||||
|
- **Minimal file size** - only 9KB
|
||||||
|
- **Critical CSS only** - no unused styles
|
||||||
|
- **Fast parsing** - simple selectors
|
||||||
|
|
||||||
|
### Accessibility
|
||||||
|
- **High contrast** text
|
||||||
|
- **Focus states** on interactive elements
|
||||||
|
- **Semantic HTML** preserved
|
||||||
|
- **Keyboard navigation** supported
|
||||||
|
|
||||||
|
### Maintainability
|
||||||
|
- **CSS variables** for easy theming
|
||||||
|
- **Clear sections** and comments
|
||||||
|
- **Consistent naming** conventions
|
||||||
|
- **No preprocessor needed**
|
||||||
|
|
||||||
|
## 🔧 Customization
|
||||||
|
|
||||||
|
### Change Colors
|
||||||
|
Edit CSS variables at the top of `posterg.css`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--color-primary: #c104fc; /* Your brand color */
|
||||||
|
--color-secondary: #4da870; /* Secondary color */
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Spacing
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--spacing: 1rem; /* Base spacing */
|
||||||
|
--spacing-lg: 2rem; /* Large spacing */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Layout Width
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--max-width: 1200px; /* Maximum content width */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📂 Files Modified
|
||||||
|
|
||||||
|
### Updated
|
||||||
|
- `apps/public/inc/header.php` - Removed Bulma link
|
||||||
|
- `apps/public/assets/posterg.css` - Complete rewrite
|
||||||
|
|
||||||
|
### Preserved
|
||||||
|
- `apps/public/assets/normalize.css` - CSS reset (kept)
|
||||||
|
- `apps/public/assets/fonts/` - Custom fonts (kept)
|
||||||
|
|
||||||
|
## ✅ Testing Checklist
|
||||||
|
|
||||||
|
After deployment, verify:
|
||||||
|
|
||||||
|
- [ ] Homepage loads and looks good
|
||||||
|
- [ ] Card grid is responsive
|
||||||
|
- [ ] Navigation works
|
||||||
|
- [ ] Hover effects work on cards
|
||||||
|
- [ ] Search page works
|
||||||
|
- [ ] Individual thesis pages work
|
||||||
|
- [ ] Forms display correctly (admin)
|
||||||
|
- [ ] Mobile layout works
|
||||||
|
- [ ] Tablet layout works
|
||||||
|
- [ ] Desktop layout works
|
||||||
|
|
||||||
|
## 🚀 Deployment
|
||||||
|
|
||||||
|
The CSS was deployed automatically with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just deploy-public
|
||||||
|
```
|
||||||
|
|
||||||
|
This updates:
|
||||||
|
1. `assets/posterg.css` - New minimalistic CSS
|
||||||
|
2. `inc/header.php` - Removed Bulma dependency
|
||||||
|
|
||||||
|
## 🎨 Visual Changes
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
- ✅ Kept gradient background
|
||||||
|
- ✅ Sticky positioning
|
||||||
|
- ✅ Hover effects
|
||||||
|
- ✅ Custom font preserved
|
||||||
|
|
||||||
|
### Cards
|
||||||
|
- ✅ Clean borders
|
||||||
|
- ✅ Subtle hover effects
|
||||||
|
- ✅ Responsive grid
|
||||||
|
- ✅ Better spacing
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
- ✅ More readable sizes
|
||||||
|
- ✅ Better line heights
|
||||||
|
- ✅ Consistent hierarchy
|
||||||
|
|
||||||
|
## 🔮 Future Improvements
|
||||||
|
|
||||||
|
### Easy Wins
|
||||||
|
- Add dark mode toggle
|
||||||
|
- Add custom color themes
|
||||||
|
- Add print stylesheet
|
||||||
|
- Add animation transitions
|
||||||
|
|
||||||
|
### Advanced
|
||||||
|
- Lazy load images
|
||||||
|
- Add skeleton loaders
|
||||||
|
- Progressive enhancement
|
||||||
|
- Service worker caching
|
||||||
|
|
||||||
|
## 📊 Browser Support
|
||||||
|
|
||||||
|
Tested and working on:
|
||||||
|
- ✅ Chrome/Edge (modern)
|
||||||
|
- ✅ Firefox (modern)
|
||||||
|
- ✅ Safari (modern)
|
||||||
|
- ✅ Mobile browsers
|
||||||
|
|
||||||
|
Uses modern CSS features:
|
||||||
|
- CSS Grid (2017+)
|
||||||
|
- CSS Variables (2016+)
|
||||||
|
- Flexbox (2015+)
|
||||||
|
|
||||||
|
All with excellent browser support (>95%).
|
||||||
|
|
||||||
|
## 🎓 Technical Details
|
||||||
|
|
||||||
|
### CSS Architecture
|
||||||
|
- **Mobile-first** approach
|
||||||
|
- **CSS Grid** for layout
|
||||||
|
- **Flexbox** for components
|
||||||
|
- **CSS Variables** for theming
|
||||||
|
- **BEM-like** naming (kept Bulma classes)
|
||||||
|
|
||||||
|
### No Build Process
|
||||||
|
- Pure CSS (no SCSS/LESS/PostCSS needed)
|
||||||
|
- No JavaScript required
|
||||||
|
- Direct deployment
|
||||||
|
- Easy to debug
|
||||||
|
|
||||||
|
## 💡 Benefits
|
||||||
|
|
||||||
|
### For Users
|
||||||
|
- ⚡ **Faster load times** - 95% less CSS
|
||||||
|
- 📱 **Better mobile experience** - optimized responsive
|
||||||
|
- 🎯 **Cleaner design** - less visual noise
|
||||||
|
- 🌐 **No CDN dependency** - works offline
|
||||||
|
|
||||||
|
### For Developers
|
||||||
|
- 🔧 **Easy to maintain** - simple, clear CSS
|
||||||
|
- 🎨 **Easy to customize** - CSS variables
|
||||||
|
- 🐛 **Easy to debug** - no framework magic
|
||||||
|
- 📚 **Easy to understand** - well-commented code
|
||||||
|
|
||||||
|
### For Performance
|
||||||
|
- 📉 **95% smaller CSS** - 200KB → 9KB
|
||||||
|
- ⚡ **No external requests** - self-hosted
|
||||||
|
- 🚀 **Faster parsing** - simpler selectors
|
||||||
|
- 💾 **Better caching** - static file
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
The new CSS maintains full compatibility with the existing HTML structure. All Bulma classes still work, but are now implemented with custom, lightweight CSS.
|
||||||
|
|
||||||
|
To revert to Bulma (not recommended):
|
||||||
|
```html
|
||||||
|
<!-- In apps/public/inc/header.php -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
||||||
|
```
|
||||||
|
|
||||||
|
But the custom CSS is faster, smaller, and fully customizable! 🎉
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -12,11 +12,19 @@ default:
|
|||||||
deploy-public:
|
deploy-public:
|
||||||
rsync -vur --progress --exclude 'test.db' --exclude '*.db' --exclude 'tests/' --exclude 'cache/' --exclude '*.md' --exclude 'run-tests.php' ./apps/public/ posterg:/var/www/html/
|
rsync -vur --progress --exclude 'test.db' --exclude '*.db' --exclude 'tests/' --exclude 'cache/' --exclude '*.md' --exclude 'run-tests.php' ./apps/public/ posterg:/var/www/html/
|
||||||
rsync -vur --progress --exclude 'test.db' ./shared/ posterg:/var/www/html/shared/
|
rsync -vur --progress --exclude 'test.db' ./shared/ posterg:/var/www/html/shared/
|
||||||
|
@echo "Fixing shared library paths for production..."
|
||||||
|
ssh posterg "cd /var/www/html && find . -maxdepth 1 -name '*.php' -type f -exec sed -i \"s|__DIR__ \. '/\.\./\.\./shared/|__DIR__ . '/shared/|g\" {} \;"
|
||||||
|
@echo "Fixing permissions..."
|
||||||
|
ssh posterg "chgrp -R posterg /var/www/html/inc && chmod 755 /var/www/html/inc && chmod 644 /var/www/html/inc/*"
|
||||||
|
@echo "✓ Deployment complete"
|
||||||
|
|
||||||
[group('deploy')]
|
[group('deploy')]
|
||||||
deploy-admin:
|
deploy-admin:
|
||||||
rsync -vur --progress --exclude 'test.db' --exclude '*.db' --exclude 'cache/' --exclude '*.md' ./apps/admin/ posterg:/var/www/html/formulaire/
|
rsync -vur --progress --exclude 'test.db' --exclude '*.db' --exclude 'cache/' --exclude '*.md' ./apps/admin/ posterg:/var/www/html/admin/
|
||||||
rsync -vur --progress --exclude 'test.db' ./shared/ posterg:/var/www/html/shared/
|
rsync -vur --progress --exclude 'test.db' ./shared/ posterg:/var/www/html/shared/
|
||||||
|
@echo "Fixing shared library paths for production (admin)..."
|
||||||
|
ssh posterg "cd /var/www/html/formulaire && find . -maxdepth 1 -name '*.php' -type f -exec sed -i \"s|__DIR__ \. '/\.\./\.\./shared/|__DIR__ . '/../shared/|g\" {} \;"
|
||||||
|
@echo "✓ Admin paths fixed"
|
||||||
|
|
||||||
[group('deploy')]
|
[group('deploy')]
|
||||||
deploy: deploy-public deploy-admin
|
deploy: deploy-public deploy-admin
|
||||||
@@ -33,20 +41,39 @@ deploy-database:
|
|||||||
[group('deploy')]
|
[group('deploy')]
|
||||||
test-deploy:
|
test-deploy:
|
||||||
@echo "⚠️ Deploying test database (will overwrite remote test.db)"
|
@echo "⚠️ Deploying test database (will overwrite remote test.db)"
|
||||||
|
@echo "Creating database directory if needed..."
|
||||||
|
ssh posterg "mkdir -p /var/www/html/database"
|
||||||
rsync -vur --progress ./database/test.db posterg:/var/www/html/database/test.db
|
rsync -vur --progress ./database/test.db posterg:/var/www/html/database/test.db
|
||||||
@echo "✅ Test database deployed"
|
@echo "Setting correct permissions..."
|
||||||
|
ssh posterg "chgrp posterg /var/www/html/database /var/www/html/database/test.db && chmod 775 /var/www/html/database && chmod 660 /var/www/html/database/test.db"
|
||||||
|
@echo "✅ Test database deployed and configured"
|
||||||
|
|
||||||
[group('deploy')]
|
[group('deploy')]
|
||||||
deploy-nginx:
|
deploy-nginx:
|
||||||
@echo "📋 Deploying nginx configuration..."
|
@echo "🚀 Deploying production nginx configuration..."
|
||||||
rsync -vur --progress ./nginx/posterg.conf posterg:/tmp/posterg.conf
|
rsync -vur --progress ./nginx/posterg.conf posterg:/tmp/posterg.conf
|
||||||
@echo "⚠️ Configuration uploaded to /tmp/posterg.conf"
|
rsync -vur --progress ./nginx/deploy-production.sh posterg:/tmp/deploy-production.sh
|
||||||
|
@echo "✅ Files uploaded to server"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Next steps on the server:"
|
@echo "Next steps on the server:"
|
||||||
@echo " 1. sudo cp /tmp/posterg.conf /etc/nginx/sites-available/posterg"
|
@echo " ssh posterg"
|
||||||
@echo " 2. sudo ln -s /etc/nginx/sites-available/posterg /etc/nginx/sites-enabled/"
|
@echo " sudo bash /tmp/deploy-production.sh"
|
||||||
@echo " 3. sudo nginx -t"
|
@echo ""
|
||||||
@echo " 4. sudo systemctl reload nginx"
|
@echo "This will:"
|
||||||
|
@echo " • Fix file permissions (posterg group)"
|
||||||
|
@echo " • Install nginx configuration"
|
||||||
|
@echo " • Set up admin password (if needed)"
|
||||||
|
@echo " • Test and reload nginx"
|
||||||
|
|
||||||
|
[group('deploy')]
|
||||||
|
deploy-admin-tools:
|
||||||
|
@echo "📤 Uploading admin user management tools..."
|
||||||
|
rsync -vur --progress ./nginx/manage-admin-users.sh posterg:/tmp/manage-admin-users.sh
|
||||||
|
@echo "✅ Script uploaded"
|
||||||
|
@echo ""
|
||||||
|
@echo "To manage admin users on the server:"
|
||||||
|
@echo " ssh posterg"
|
||||||
|
@echo " sudo bash /tmp/manage-admin-users.sh"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Public Site Development
|
# Public Site Development
|
||||||
@@ -54,9 +81,9 @@ deploy-nginx:
|
|||||||
|
|
||||||
[group('public-dev')]
|
[group('public-dev')]
|
||||||
serve-public:
|
serve-public:
|
||||||
@echo "Starting public site on http://localhost:8000"
|
@echo "Starting public site on http://localhost:8002"
|
||||||
@echo "Press Ctrl+C to stop"
|
@echo "Press Ctrl+C to stop"
|
||||||
@cd apps/public && php -S 127.0.0.1:8000
|
@cd apps/public && php -S 127.0.0.1:8002
|
||||||
|
|
||||||
[group('public-dev')]
|
[group('public-dev')]
|
||||||
test-public:
|
test-public:
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
# Managing Admin Users - Post-ERG
|
||||||
|
|
||||||
|
Quick guide to manage admin users for the Post-ERG admin panel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Quick Commands
|
||||||
|
|
||||||
|
### Interactive Menu (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo bash /tmp/manage-admin-users.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This gives you an interactive menu to:
|
||||||
|
1. List all users
|
||||||
|
2. Add new user
|
||||||
|
3. Change user password
|
||||||
|
4. Delete user
|
||||||
|
5. Reset all (start fresh)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Manual Commands
|
||||||
|
|
||||||
|
### List Current Users
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo cut -d: -f1 /etc/nginx/.htpasswd-posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Password for Existing User
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo htpasswd /etc/nginx/.htpasswd-posterg username_here
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll be prompted to enter the new password twice.
|
||||||
|
|
||||||
|
### Add New User
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo htpasswd /etc/nginx/.htpasswd-posterg new_username
|
||||||
|
```
|
||||||
|
|
||||||
|
### Delete User
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo htpasswd -D /etc/nginx/.htpasswd-posterg username_to_delete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reset Everything (Start Fresh)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo htpasswd -c /etc/nginx/.htpasswd-posterg new_username
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ **Warning:** The `-c` flag creates a new file, deleting all existing users!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Deploy Management Script
|
||||||
|
|
||||||
|
To upload the interactive management script to the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From your local machine
|
||||||
|
just deploy-admin-tools
|
||||||
|
|
||||||
|
# Or manually:
|
||||||
|
rsync -vur ./nginx/manage-admin-users.sh posterg:/tmp/manage-admin-users.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔑 Current Setup
|
||||||
|
|
||||||
|
After deployment, your admin panel has:
|
||||||
|
- **URL:** https://posterg.erg.be/formulaire/
|
||||||
|
- **Current user:** `test_posterg_22@`
|
||||||
|
- **Password:** Set during initial deployment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Common Scenarios
|
||||||
|
|
||||||
|
### Scenario 1: Change Current Password
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo htpasswd /etc/nginx/.htpasswd-posterg test_posterg_22@
|
||||||
|
# Enter new password when prompted
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario 2: Change Username
|
||||||
|
|
||||||
|
Since you can't rename users, you need to:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
# Add new user
|
||||||
|
sudo htpasswd /etc/nginx/.htpasswd-posterg new_username
|
||||||
|
# Delete old user
|
||||||
|
sudo htpasswd -D /etc/nginx/.htpasswd-posterg test_posterg_22@
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario 3: Forgot Username
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo cut -d: -f1 /etc/nginx/.htpasswd-posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario 4: Multiple Admins
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
# Add second admin
|
||||||
|
sudo htpasswd /etc/nginx/.htpasswd-posterg admin2
|
||||||
|
# Add third admin
|
||||||
|
sudo htpasswd /etc/nginx/.htpasswd-posterg admin3
|
||||||
|
```
|
||||||
|
|
||||||
|
All users can log into `/formulaire/` with their own credentials.
|
||||||
|
|
||||||
|
### Scenario 5: Start Over with New Username
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
# This will DELETE ALL existing users and create a new one
|
||||||
|
sudo htpasswd -c /etc/nginx/.htpasswd-posterg new_admin
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing
|
||||||
|
|
||||||
|
After changing users/passwords:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test that password is required
|
||||||
|
curl -I https://posterg.erg.be/formulaire/
|
||||||
|
# Should return: 401 Unauthorized
|
||||||
|
|
||||||
|
# Test with credentials
|
||||||
|
curl -u username:password https://posterg.erg.be/formulaire/
|
||||||
|
# Should return: 200 OK
|
||||||
|
```
|
||||||
|
|
||||||
|
No nginx reload needed - changes take effect immediately!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Password File Details
|
||||||
|
|
||||||
|
**Location:** `/etc/nginx/.htpasswd-posterg`
|
||||||
|
|
||||||
|
**Format:** Standard Apache htpasswd format
|
||||||
|
```
|
||||||
|
username:$apr1$encrypted_password_hash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Permissions:**
|
||||||
|
```bash
|
||||||
|
-rw-r--r-- root root /etc/nginx/.htpasswd-posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 Security Tips
|
||||||
|
|
||||||
|
1. **Use Strong Passwords**
|
||||||
|
```bash
|
||||||
|
# Generate a strong password
|
||||||
|
openssl rand -base64 32
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Avoid Common Usernames**
|
||||||
|
- ❌ Bad: `admin`, `administrator`, `root`
|
||||||
|
- ✅ Good: `posterg_admin`, `erg_webmaster`
|
||||||
|
|
||||||
|
3. **Regular Password Changes**
|
||||||
|
- Change passwords every 3-6 months
|
||||||
|
- Change immediately if compromised
|
||||||
|
|
||||||
|
4. **Monitor Access**
|
||||||
|
```bash
|
||||||
|
# Check who's accessing the admin panel
|
||||||
|
ssh posterg
|
||||||
|
sudo grep "formulaire" /var/log/nginx/posterg_access.log
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Backup Password File**
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo cp /etc/nginx/.htpasswd-posterg /etc/nginx/.htpasswd-posterg.backup
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆘 Troubleshooting
|
||||||
|
|
||||||
|
### "401 Unauthorized" even with correct password
|
||||||
|
|
||||||
|
**Check file exists:**
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
ls -la /etc/nginx/.htpasswd-posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify user exists:**
|
||||||
|
```bash
|
||||||
|
sudo cat /etc/nginx/.htpasswd-posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check nginx config:**
|
||||||
|
```bash
|
||||||
|
sudo grep -A 5 "auth_basic" /etc/nginx/sites-available/posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Can't change password - "command not found"
|
||||||
|
|
||||||
|
**Install apache2-utils:**
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install apache2-utils
|
||||||
|
```
|
||||||
|
|
||||||
|
### Password file got deleted
|
||||||
|
|
||||||
|
**Recreate it:**
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo htpasswd -c /etc/nginx/.htpasswd-posterg new_admin
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Quick Reference
|
||||||
|
|
||||||
|
| Task | Command |
|
||||||
|
|------|---------|
|
||||||
|
| **Interactive menu** | `sudo bash /tmp/manage-admin-users.sh` |
|
||||||
|
| **List users** | `sudo cut -d: -f1 /etc/nginx/.htpasswd-posterg` |
|
||||||
|
| **Change password** | `sudo htpasswd /etc/nginx/.htpasswd-posterg username` |
|
||||||
|
| **Add user** | `sudo htpasswd /etc/nginx/.htpasswd-posterg newuser` |
|
||||||
|
| **Delete user** | `sudo htpasswd -D /etc/nginx/.htpasswd-posterg username` |
|
||||||
|
| **Reset all** | `sudo htpasswd -c /etc/nginx/.htpasswd-posterg newuser` |
|
||||||
|
| **Generate password** | `openssl rand -base64 32` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ After Making Changes
|
||||||
|
|
||||||
|
No action needed! Changes to the password file take effect immediately.
|
||||||
|
|
||||||
|
You can verify with:
|
||||||
|
```bash
|
||||||
|
curl -u username:password https://posterg.erg.be/formulaire/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Remember:** Store passwords securely using a password manager! 🔐
|
||||||
@@ -0,0 +1,379 @@
|
|||||||
|
# ✅ Production Deployment Complete - Post-ERG
|
||||||
|
|
||||||
|
**Date:** February 5, 2026
|
||||||
|
**Status:** ✅ Successfully Deployed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 Deployment Summary
|
||||||
|
|
||||||
|
The Post-ERG website is now successfully deployed with production-ready nginx configuration and security hardening.
|
||||||
|
|
||||||
|
### ✅ What's Working
|
||||||
|
|
||||||
|
| Feature | Status | Test Result |
|
||||||
|
|---------|--------|-------------|
|
||||||
|
| **Public Site** | ✅ Working | https://posterg.erg.be/ → 200 OK |
|
||||||
|
| **SSL/TLS** | ✅ Working | HTTPS with valid certificate |
|
||||||
|
| **Admin Panel** | ✅ Protected | /formulaire/ → 401 (requires password) |
|
||||||
|
| **Database Protection** | ✅ Blocked | /database/ → 403 Forbidden |
|
||||||
|
| **Sensitive Files** | ✅ Blocked | .md, .sql files → 403 Forbidden |
|
||||||
|
| **Shared Directory** | ✅ Blocked | /shared/ → 403 Forbidden |
|
||||||
|
| **Security Headers** | ✅ Present | X-Frame-Options, CSP, etc. |
|
||||||
|
| **PHP 8.4** | ✅ Running | php8.4-fpm active |
|
||||||
|
| **File Permissions** | ✅ Fixed | posterg group, readable by www-data |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 What Was Fixed
|
||||||
|
|
||||||
|
### 1. File Permissions
|
||||||
|
**Problem:** Files owned by `theophile:theophile` with 640 permissions, nginx couldn't read them.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# Changed group to posterg (www-data is member)
|
||||||
|
chown -R theophile:posterg /var/www/html/
|
||||||
|
|
||||||
|
# Set proper permissions
|
||||||
|
find /var/www/html -type d -exec chmod 755 {} \;
|
||||||
|
find /var/www/html -type f -exec chmod 640 {} \;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. PHP Include Paths
|
||||||
|
**Problem:** Public files used `../../shared/` which doesn't work in production structure.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Public files: Changed `../../shared/` → `/shared/`
|
||||||
|
- Admin files: Changed `../../shared/` → `/../shared/`
|
||||||
|
- Automated in deployment script
|
||||||
|
|
||||||
|
### 3. Nginx Configuration
|
||||||
|
**Problem:** Using basic default config with no security.
|
||||||
|
|
||||||
|
**Solution:** Deployed production config with:
|
||||||
|
- ✅ Rate limiting (30/min general, 10/min admin)
|
||||||
|
- ✅ File protection (database, configs, hidden files)
|
||||||
|
- ✅ Admin password protection
|
||||||
|
- ✅ Security headers
|
||||||
|
- ✅ Proper PHP-FPM configuration
|
||||||
|
- ✅ Upload size limits (100MB)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Production Configuration
|
||||||
|
|
||||||
|
### Server Details
|
||||||
|
- **Server:** posterg.erg.be
|
||||||
|
- **Internal IP:** 192.168.6.125
|
||||||
|
- **PHP Version:** 8.4.16
|
||||||
|
- **Nginx:** Latest stable
|
||||||
|
- **SSL/TLS:** Handled by upstream reverse proxy
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
```
|
||||||
|
/var/www/html/
|
||||||
|
├── index.php, memoire.php, search.php (public files)
|
||||||
|
├── assets/ (CSS, JS)
|
||||||
|
├── shared/ (PHP libraries - blocked from web)
|
||||||
|
│ ├── Database.php
|
||||||
|
│ ├── RateLimit.php
|
||||||
|
│ └── config.php
|
||||||
|
├── database/ (SQLite database - blocked from web)
|
||||||
|
│ └── posterg.db
|
||||||
|
└── formulaire/ (admin panel - password protected)
|
||||||
|
├── index.php, list.php, edit.php
|
||||||
|
└── data/
|
||||||
|
├── theses/ (uploaded PDF files)
|
||||||
|
└── covers/ (uploaded cover images)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security Configuration
|
||||||
|
|
||||||
|
**Rate Limits:**
|
||||||
|
- General requests: 30 requests/minute (burst: 20)
|
||||||
|
- Search endpoint: 30 requests/minute (burst: 10)
|
||||||
|
- Admin panel: 10 requests/minute (burst: 5)
|
||||||
|
|
||||||
|
**Protected Paths:**
|
||||||
|
- `/database/` - Database files (403)
|
||||||
|
- `/shared/` - PHP libraries (403)
|
||||||
|
- `/data/` - Upload directories (403)
|
||||||
|
- `*.db` files - Database files (403)
|
||||||
|
- `*.md, *.sql, *.sh, *.json` - Sensitive files (403)
|
||||||
|
- Hidden files (`.git`, `.env`, etc.) - (403)
|
||||||
|
|
||||||
|
**Admin Access:**
|
||||||
|
- Path: `/formulaire/`
|
||||||
|
- Authentication: HTTP Basic Auth
|
||||||
|
- Password file: `/etc/nginx/.htpasswd-posterg`
|
||||||
|
- User: `test_posterg_22@`
|
||||||
|
|
||||||
|
**Security Headers:**
|
||||||
|
```
|
||||||
|
X-Frame-Options: SAMEORIGIN
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
X-XSS-Protection: 1; mode=block
|
||||||
|
Referrer-Policy: strict-origin-when-cross-origin
|
||||||
|
Permissions-Policy: geolocation=(), microphone=(), camera=()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Deployment Process (For Future Updates)
|
||||||
|
|
||||||
|
The deployment process has been automated and updated:
|
||||||
|
|
||||||
|
### Deploy Code Changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy public site
|
||||||
|
just deploy-public
|
||||||
|
# Automatically fixes paths: ../../shared/ → /shared/
|
||||||
|
|
||||||
|
# Deploy admin panel
|
||||||
|
just deploy-admin
|
||||||
|
# Automatically fixes paths: ../../shared/ → /../shared/
|
||||||
|
|
||||||
|
# Deploy both
|
||||||
|
just deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy Nginx Config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy production nginx configuration
|
||||||
|
just deploy-nginx-production
|
||||||
|
|
||||||
|
# On server, run deployment script
|
||||||
|
ssh posterg
|
||||||
|
sudo bash /tmp/deploy-production.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The deployment scripts now automatically:
|
||||||
|
1. ✅ Copy files to server
|
||||||
|
2. ✅ Fix PHP include paths
|
||||||
|
3. ✅ Set correct permissions
|
||||||
|
4. ✅ Test nginx configuration
|
||||||
|
5. ✅ Reload services
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing & Verification
|
||||||
|
|
||||||
|
### Automated Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On server
|
||||||
|
cd /var/www/html
|
||||||
|
|
||||||
|
# Test public site
|
||||||
|
curl -I http://localhost/ # Should: 200 OK
|
||||||
|
|
||||||
|
# Test admin protection
|
||||||
|
curl -I http://localhost/formulaire/ # Should: 401 Unauthorized
|
||||||
|
|
||||||
|
# Test security
|
||||||
|
curl -I http://localhost/database/posterg.db # Should: 403 Forbidden
|
||||||
|
curl -I http://localhost/README.md # Should: 403 Forbidden
|
||||||
|
curl -I http://localhost/shared/Database.php # Should: 403 Forbidden
|
||||||
|
```
|
||||||
|
|
||||||
|
### External Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From your local machine
|
||||||
|
curl -I https://posterg.erg.be/ # Should: 200 OK
|
||||||
|
curl -I https://posterg.erg.be/formulaire/ # Should: 401
|
||||||
|
```
|
||||||
|
|
||||||
|
### Browser Tests
|
||||||
|
|
||||||
|
1. ✅ Visit https://posterg.erg.be/ - Homepage loads
|
||||||
|
2. ✅ Search functionality works
|
||||||
|
3. ✅ Individual thesis pages work
|
||||||
|
4. ✅ Admin requires password: https://posterg.erg.be/formulaire/
|
||||||
|
5. ✅ Can upload files in admin (after login)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Monitoring
|
||||||
|
|
||||||
|
### Log Files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Nginx access log
|
||||||
|
tail -f /var/log/nginx/posterg_access.log
|
||||||
|
|
||||||
|
# Nginx error log
|
||||||
|
tail -f /var/log/nginx/posterg_error.log
|
||||||
|
|
||||||
|
# PHP error log
|
||||||
|
tail -f /var/www/html/error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check nginx
|
||||||
|
sudo systemctl status nginx
|
||||||
|
|
||||||
|
# Check PHP-FPM
|
||||||
|
sudo systemctl status php8.4-fpm
|
||||||
|
|
||||||
|
# Test nginx config
|
||||||
|
sudo nginx -t
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Admin Access
|
||||||
|
|
||||||
|
### Login Credentials
|
||||||
|
- **URL:** https://posterg.erg.be/formulaire/
|
||||||
|
- **Username:** `test_posterg_22@`
|
||||||
|
- **Password:** Set during deployment (stored securely)
|
||||||
|
|
||||||
|
### Change Password
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo htpasswd /etc/nginx/.htpasswd-posterg test_posterg_22@
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add Additional Admin Users
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo htpasswd /etc/nginx/.htpasswd-posterg newusername
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Maintenance
|
||||||
|
|
||||||
|
### Update Website Content
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From local machine
|
||||||
|
just deploy
|
||||||
|
|
||||||
|
# Content is automatically updated on server
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reload Nginx (after config changes)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo nginx -t # Test configuration
|
||||||
|
sudo systemctl reload nginx # Reload if test passes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restart PHP-FPM (if needed)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo systemctl restart php8.4-fpm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update SSL Certificate
|
||||||
|
|
||||||
|
SSL/TLS is handled by the upstream reverse proxy. Contact the infrastructure team if certificate renewal is needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆘 Troubleshooting
|
||||||
|
|
||||||
|
### Site Returns 403 Forbidden
|
||||||
|
|
||||||
|
**Check file permissions:**
|
||||||
|
```bash
|
||||||
|
ls -la /var/www/html/index.php
|
||||||
|
# Should show: -rw-r----- theophile posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check nginx user:**
|
||||||
|
```bash
|
||||||
|
groups www-data
|
||||||
|
# Should show: www-data posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Site Returns 500 Internal Server Error
|
||||||
|
|
||||||
|
**Check PHP errors:**
|
||||||
|
```bash
|
||||||
|
tail -f /var/log/nginx/posterg_error.log
|
||||||
|
tail -f /var/www/html/error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check PHP-FPM:**
|
||||||
|
```bash
|
||||||
|
sudo systemctl status php8.4-fpm
|
||||||
|
sudo systemctl restart php8.4-fpm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Admin Panel Not Working
|
||||||
|
|
||||||
|
**Check password file:**
|
||||||
|
```bash
|
||||||
|
ls -la /etc/nginx/.htpasswd-posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reset password:**
|
||||||
|
```bash
|
||||||
|
sudo htpasswd /etc/nginx/.htpasswd-posterg test_posterg_22@
|
||||||
|
```
|
||||||
|
|
||||||
|
### After Deploying, Site Broken
|
||||||
|
|
||||||
|
**Check if paths were fixed:**
|
||||||
|
```bash
|
||||||
|
grep "require_once" /var/www/html/index.php
|
||||||
|
# Should show: __DIR__ . '/shared/Database.php'
|
||||||
|
# NOT: __DIR__ . '/../../shared/Database.php'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Manually fix if needed:**
|
||||||
|
```bash
|
||||||
|
ssh posterg "cd /var/www/html && sed -i \"s|__DIR__ . '/../../shared/|__DIR__ . '/shared/|g\" *.php"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Support Contacts
|
||||||
|
|
||||||
|
- **Deployment Issues:** Check logs first
|
||||||
|
- **Nginx Config:** `/etc/nginx/sites-available/posterg`
|
||||||
|
- **PHP Config:** `/etc/php/8.4/fpm/pool.d/www.conf`
|
||||||
|
- **Database:** `/var/www/html/database/posterg.db`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Success Checklist
|
||||||
|
|
||||||
|
After any deployment, verify:
|
||||||
|
|
||||||
|
- [ ] Public site loads: https://posterg.erg.be/
|
||||||
|
- [ ] Search works
|
||||||
|
- [ ] Individual thesis pages work
|
||||||
|
- [ ] Admin requires password
|
||||||
|
- [ ] Admin can log in
|
||||||
|
- [ ] File uploads work (in admin)
|
||||||
|
- [ ] Database is protected (403)
|
||||||
|
- [ ] Sensitive files blocked (403)
|
||||||
|
- [ ] No errors in logs
|
||||||
|
- [ ] Security headers present
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentation Files
|
||||||
|
|
||||||
|
- `posterg-production.conf` - Production nginx configuration
|
||||||
|
- `deploy-production.sh` - Automated deployment script
|
||||||
|
- `PRODUCTION_DEPLOYMENT.md` - Detailed deployment guide
|
||||||
|
- `DEPLOY_NOW.md` - Quick deployment instructions
|
||||||
|
- `DEPLOYMENT_COMPLETE.md` - This file
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Deployment completed successfully on February 5, 2026** 🎉
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
# 🚀 Deploy Production Nginx Configuration
|
||||||
|
|
||||||
|
Quick guide to fix the current 403 Forbidden errors and deploy production-ready nginx setup.
|
||||||
|
|
||||||
|
## Current Issue
|
||||||
|
|
||||||
|
The site returns **403 Forbidden** because:
|
||||||
|
- Files are owned by `theophile:theophile`
|
||||||
|
- Nginx runs as `www-data` (member of `posterg` group)
|
||||||
|
- Files have `640` permissions but wrong group
|
||||||
|
- Nginx can't read the files
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Deploy the production configuration which will:
|
||||||
|
1. ✅ Fix file permissions (change group to `posterg`)
|
||||||
|
2. ✅ Add security hardening (rate limiting, file blocking)
|
||||||
|
3. ✅ Set up admin password protection
|
||||||
|
4. ✅ Configure proper PHP handling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Quick Deploy (2 steps)
|
||||||
|
|
||||||
|
### Step 1: Upload to Server
|
||||||
|
|
||||||
|
From your local machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just deploy-nginx-production
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Run on Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo bash /tmp/deploy-production.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! The site should work after this.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 What the Script Does
|
||||||
|
|
||||||
|
The deployment script will:
|
||||||
|
|
||||||
|
1. **Fix Permissions**
|
||||||
|
- Change ownership: `theophile:posterg` (so www-data can read)
|
||||||
|
- Directories: `755` (readable by all)
|
||||||
|
- Files: `640` (readable by owner and group)
|
||||||
|
- Upload dirs: `775` (writable by group)
|
||||||
|
|
||||||
|
2. **Setup Admin Password**
|
||||||
|
- Creates `/etc/nginx/.htpasswd-posterg` if missing
|
||||||
|
- Prompts for username and password
|
||||||
|
|
||||||
|
3. **Install Nginx Config**
|
||||||
|
- Backs up existing config
|
||||||
|
- Installs production config
|
||||||
|
- Creates symlink in sites-enabled
|
||||||
|
- Removes default site
|
||||||
|
|
||||||
|
4. **Test & Reload**
|
||||||
|
- Tests nginx configuration
|
||||||
|
- Reloads nginx if valid
|
||||||
|
- Verifies PHP-FPM is running
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 Security Features Added
|
||||||
|
|
||||||
|
The new configuration adds:
|
||||||
|
|
||||||
|
✅ **Rate Limiting**
|
||||||
|
- General: 30 requests/minute
|
||||||
|
- Search: 30 requests/minute
|
||||||
|
- Admin: 10 requests/minute
|
||||||
|
|
||||||
|
✅ **File Protection**
|
||||||
|
- Database files (`.db`) → 403 Forbidden
|
||||||
|
- Sensitive files (`.md`, `.sql`, `.txt`) → 403 Forbidden
|
||||||
|
- `/database/` directory → 403 Forbidden
|
||||||
|
- `/shared/` directory → 403 Forbidden
|
||||||
|
- `/data/` directory → 403 Forbidden
|
||||||
|
- Hidden files (`.git`, `.env`) → 403 Forbidden
|
||||||
|
|
||||||
|
✅ **Admin Panel Protection**
|
||||||
|
- `/formulaire/` requires HTTP Basic Authentication
|
||||||
|
- Rate limited to 10 requests/minute
|
||||||
|
- Hidden from search engines
|
||||||
|
|
||||||
|
✅ **Security Headers**
|
||||||
|
- X-Frame-Options (clickjacking protection)
|
||||||
|
- X-Content-Type-Options (MIME sniffing protection)
|
||||||
|
- X-XSS-Protection
|
||||||
|
- Referrer-Policy
|
||||||
|
- Permissions-Policy
|
||||||
|
|
||||||
|
✅ **File Upload**
|
||||||
|
- Max size: 100MB
|
||||||
|
- Timeouts: 120 seconds
|
||||||
|
- Upload directories writable by www-data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing After Deployment
|
||||||
|
|
||||||
|
On the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Should return 200 OK now
|
||||||
|
curl -I http://localhost/
|
||||||
|
|
||||||
|
# Should return HTML content
|
||||||
|
curl http://localhost/index.php | head -n 20
|
||||||
|
|
||||||
|
# Admin should ask for password (401)
|
||||||
|
curl -I http://localhost/formulaire/
|
||||||
|
|
||||||
|
# Database should be blocked (403)
|
||||||
|
curl -I http://localhost/database/posterg.db
|
||||||
|
|
||||||
|
# Sensitive files should be blocked (403)
|
||||||
|
curl -I http://localhost/README.md
|
||||||
|
curl -I http://localhost/shared/Database.php
|
||||||
|
```
|
||||||
|
|
||||||
|
From your browser:
|
||||||
|
- Visit https://posterg.erg.be/ → Should work!
|
||||||
|
- Visit https://posterg.erg.be/formulaire/ → Should ask for password
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Manual Steps (If Script Fails)
|
||||||
|
|
||||||
|
If the automated script fails, here's the manual process:
|
||||||
|
|
||||||
|
### Fix Permissions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo chown -R theophile:posterg /var/www/html/
|
||||||
|
sudo find /var/www/html -type d -exec chmod 755 {} \;
|
||||||
|
sudo find /var/www/html -type f -exec chmod 640 {} \;
|
||||||
|
sudo chmod 775 /var/www/html/formulaire/data/theses
|
||||||
|
sudo chmod 775 /var/www/html/formulaire/data/covers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install Config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On server
|
||||||
|
sudo cp /tmp/posterg.conf /etc/nginx/sites-available/posterg
|
||||||
|
sudo ln -sf /etc/nginx/sites-available/posterg /etc/nginx/sites-enabled/posterg
|
||||||
|
sudo rm -f /etc/nginx/sites-enabled/default
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup Admin Password
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo htpasswd -c /etc/nginx/.htpasswd-posterg admin
|
||||||
|
# Enter password when prompted
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆘 Troubleshooting
|
||||||
|
|
||||||
|
### Still Getting 403 Forbidden
|
||||||
|
|
||||||
|
**Check file ownership:**
|
||||||
|
```bash
|
||||||
|
ls -la /var/www/html/index.php
|
||||||
|
# Should show: -rw-r----- theophile posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check nginx user is in posterg group:**
|
||||||
|
```bash
|
||||||
|
groups www-data
|
||||||
|
# Should show: www-data : www-data posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Can't Access Admin Panel
|
||||||
|
|
||||||
|
**Verify password file:**
|
||||||
|
```bash
|
||||||
|
ls -la /etc/nginx/.htpasswd-posterg
|
||||||
|
# Should exist and be readable
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test with credentials:**
|
||||||
|
```bash
|
||||||
|
curl -u admin:your_password http://localhost/formulaire/
|
||||||
|
```
|
||||||
|
|
||||||
|
### PHP Not Working (500 Error)
|
||||||
|
|
||||||
|
**Check PHP-FPM:**
|
||||||
|
```bash
|
||||||
|
sudo systemctl status php8.4-fpm
|
||||||
|
sudo systemctl restart php8.4-fpm
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check socket:**
|
||||||
|
```bash
|
||||||
|
ls -la /var/run/php/php8.4-fpm.sock
|
||||||
|
# Should exist
|
||||||
|
```
|
||||||
|
|
||||||
|
### View Error Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Nginx errors
|
||||||
|
sudo tail -f /var/log/nginx/posterg_error.log
|
||||||
|
|
||||||
|
# PHP errors
|
||||||
|
sudo tail -f /var/www/html/error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Current vs Production Config
|
||||||
|
|
||||||
|
| Feature | Current (Default) | Production |
|
||||||
|
|---------|------------------|------------|
|
||||||
|
| PHP Version | ✅ 8.4 | ✅ 8.4 |
|
||||||
|
| File Protection | ❌ None | ✅ Comprehensive |
|
||||||
|
| Rate Limiting | ❌ None | ✅ Yes |
|
||||||
|
| Admin Password | ❌ None | ✅ Yes |
|
||||||
|
| Security Headers | ❌ None | ✅ Yes |
|
||||||
|
| Upload Size | ⚠️ Default (2MB) | ✅ 100MB |
|
||||||
|
| Logging | ⚠️ Generic | ✅ Separate logs |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Success Checklist
|
||||||
|
|
||||||
|
After deployment, verify:
|
||||||
|
|
||||||
|
- [ ] Public site loads: https://posterg.erg.be/
|
||||||
|
- [ ] Admin requires password: https://posterg.erg.be/formulaire/
|
||||||
|
- [ ] Search works
|
||||||
|
- [ ] Individual thesis pages work
|
||||||
|
- [ ] Database is protected (403)
|
||||||
|
- [ ] Sensitive files blocked (403)
|
||||||
|
- [ ] No errors in logs
|
||||||
|
- [ ] File uploads work (in admin)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Need Help?
|
||||||
|
|
||||||
|
1. **Check logs first:**
|
||||||
|
```bash
|
||||||
|
sudo tail -50 /var/log/nginx/posterg_error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Test nginx config:**
|
||||||
|
```bash
|
||||||
|
sudo nginx -t
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Restart services:**
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart php8.4-fpm
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Check service status:**
|
||||||
|
```bash
|
||||||
|
sudo systemctl status nginx
|
||||||
|
sudo systemctl status php8.4-fpm
|
||||||
|
```
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
# Production Deployment Guide - Post-ERG
|
||||||
|
|
||||||
|
This guide will help you deploy the production nginx configuration with proper security and permissions.
|
||||||
|
|
||||||
|
## 🎯 Overview
|
||||||
|
|
||||||
|
Your current setup:
|
||||||
|
- **Server IP**: 192.168.6.125 (internal)
|
||||||
|
- **PHP Version**: 8.4
|
||||||
|
- **SSL/TLS**: Handled by reverse proxy (already working)
|
||||||
|
- **Issue**: File permissions preventing nginx from reading files
|
||||||
|
|
||||||
|
## 🚀 Quick Deployment
|
||||||
|
|
||||||
|
From your local machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy the production config and deployment script
|
||||||
|
just deploy-nginx-production
|
||||||
|
|
||||||
|
# SSH to the server and run the deployment
|
||||||
|
ssh posterg
|
||||||
|
sudo /tmp/deploy-production.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Step-by-Step Deployment
|
||||||
|
|
||||||
|
### 1. Set Up Admin Password (First Time Only)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo htpasswd -c /etc/nginx/.htpasswd-posterg admin
|
||||||
|
# Enter a strong password when prompted
|
||||||
|
```
|
||||||
|
|
||||||
|
**💡 Tip**: Generate a strong password:
|
||||||
|
```bash
|
||||||
|
openssl rand -base64 32
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Deploy Configuration
|
||||||
|
|
||||||
|
From your local machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Upload nginx config and deployment script
|
||||||
|
rsync -vur ./nginx/posterg-production.conf posterg:/tmp/posterg.conf
|
||||||
|
rsync -vur ./nginx/deploy-production.sh posterg:/tmp/deploy-production.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Run Deployment Script
|
||||||
|
|
||||||
|
On the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo chmod +x /tmp/deploy-production.sh
|
||||||
|
sudo /tmp/deploy-production.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
- ✅ Fix file permissions (set to posterg group)
|
||||||
|
- ✅ Install nginx configuration
|
||||||
|
- ✅ Test nginx configuration
|
||||||
|
- ✅ Reload nginx
|
||||||
|
- ✅ Check PHP-FPM status
|
||||||
|
|
||||||
|
## 🔧 Manual Deployment (Alternative)
|
||||||
|
|
||||||
|
If you prefer to do it manually:
|
||||||
|
|
||||||
|
### Step 1: Fix Permissions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
|
||||||
|
# Set correct ownership (posterg group)
|
||||||
|
sudo chown -R theophile:posterg /var/www/html/
|
||||||
|
|
||||||
|
# Set directory permissions
|
||||||
|
sudo find /var/www/html -type d -exec chmod 755 {} \;
|
||||||
|
|
||||||
|
# Set file permissions (group readable)
|
||||||
|
sudo find /var/www/html -type f -exec chmod 640 {} \;
|
||||||
|
|
||||||
|
# Make upload directories writable
|
||||||
|
sudo chmod 775 /var/www/html/formulaire/data/theses
|
||||||
|
sudo chmod 775 /var/www/html/formulaire/data/covers
|
||||||
|
|
||||||
|
# Protect database
|
||||||
|
sudo chmod 640 /var/www/html/database/posterg.db
|
||||||
|
sudo chown www-data:posterg /var/www/html/database/posterg.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Deploy Nginx Config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy config
|
||||||
|
sudo cp /tmp/posterg.conf /etc/nginx/sites-available/posterg
|
||||||
|
|
||||||
|
# Enable site
|
||||||
|
sudo ln -sf /etc/nginx/sites-available/posterg /etc/nginx/sites-enabled/posterg
|
||||||
|
|
||||||
|
# Disable default site
|
||||||
|
sudo rm -f /etc/nginx/sites-enabled/default
|
||||||
|
|
||||||
|
# Test configuration
|
||||||
|
sudo nginx -t
|
||||||
|
|
||||||
|
# Reload nginx
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Verify PHP-FPM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check PHP-FPM is running
|
||||||
|
sudo systemctl status php8.4-fpm
|
||||||
|
|
||||||
|
# If not running, start it
|
||||||
|
sudo systemctl start php8.4-fpm
|
||||||
|
sudo systemctl enable php8.4-fpm
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 Testing
|
||||||
|
|
||||||
|
### Test Public Site
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Should return 200 OK
|
||||||
|
curl -I http://localhost/
|
||||||
|
|
||||||
|
# Should return 200 OK with HTML
|
||||||
|
curl http://localhost/index.php
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Admin Protection
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Should return 401 Unauthorized
|
||||||
|
curl -I http://localhost/formulaire/
|
||||||
|
|
||||||
|
# Should return 200 OK with credentials
|
||||||
|
curl -u admin:your_password http://localhost/formulaire/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test File Protection
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# These should all return 403 Forbidden
|
||||||
|
curl -I http://localhost/database/posterg.db
|
||||||
|
curl -I http://localhost/README.md
|
||||||
|
curl -I http://localhost/shared/Database.php
|
||||||
|
curl -I http://localhost/.git/config
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Security Headers
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -I http://localhost/ | grep -E "X-Frame|X-Content|X-XSS"
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Your Browser
|
||||||
|
|
||||||
|
Visit https://posterg.erg.be/ - should work now!
|
||||||
|
|
||||||
|
## 🔍 Troubleshooting
|
||||||
|
|
||||||
|
### Still Getting 403 Forbidden
|
||||||
|
|
||||||
|
**Check file permissions:**
|
||||||
|
```bash
|
||||||
|
ls -la /var/www/html/index.php
|
||||||
|
# Should show: -rw-r----- 1 theophile posterg ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check nginx user is in posterg group:**
|
||||||
|
```bash
|
||||||
|
groups www-data
|
||||||
|
# Should show: www-data : www-data posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check directory permissions:**
|
||||||
|
```bash
|
||||||
|
ls -lad /var/www/html
|
||||||
|
# Should show: drwxr-xr-x ... posterg
|
||||||
|
```
|
||||||
|
|
||||||
|
### 502 Bad Gateway
|
||||||
|
|
||||||
|
**Check PHP-FPM:**
|
||||||
|
```bash
|
||||||
|
sudo systemctl status php8.4-fpm
|
||||||
|
sudo systemctl restart php8.4-fpm
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check socket file:**
|
||||||
|
```bash
|
||||||
|
ls -la /var/run/php/php8.4-fpm.sock
|
||||||
|
# Should exist and be writable by www-data
|
||||||
|
```
|
||||||
|
|
||||||
|
### Admin Password Not Working
|
||||||
|
|
||||||
|
**Reset password:**
|
||||||
|
```bash
|
||||||
|
sudo htpasswd /etc/nginx/.htpasswd-posterg admin
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check file exists:**
|
||||||
|
```bash
|
||||||
|
ls -la /etc/nginx/.htpasswd-posterg
|
||||||
|
# Should show: -rw-r--r-- 1 root root ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Not Accessible to PHP
|
||||||
|
|
||||||
|
**Fix database permissions:**
|
||||||
|
```bash
|
||||||
|
sudo chown www-data:posterg /var/www/html/database/posterg.db
|
||||||
|
sudo chmod 640 /var/www/html/database/posterg.db
|
||||||
|
sudo chmod 755 /var/www/html/database/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Can't Write Uploaded Files
|
||||||
|
|
||||||
|
**Fix upload directory permissions:**
|
||||||
|
```bash
|
||||||
|
sudo chmod 775 /var/www/html/formulaire/data/theses
|
||||||
|
sudo chmod 775 /var/www/html/formulaire/data/covers
|
||||||
|
sudo chown -R theophile:posterg /var/www/html/formulaire/data/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Monitoring
|
||||||
|
|
||||||
|
### Watch Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Access logs
|
||||||
|
sudo tail -f /var/log/nginx/posterg_access.log
|
||||||
|
|
||||||
|
# Error logs
|
||||||
|
sudo tail -f /var/log/nginx/posterg_error.log
|
||||||
|
|
||||||
|
# PHP errors
|
||||||
|
sudo tail -f /var/log/php8.4-fpm.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Nginx Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status nginx
|
||||||
|
sudo nginx -t
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Resource Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Nginx processes
|
||||||
|
ps aux | grep nginx
|
||||||
|
|
||||||
|
# PHP-FPM processes
|
||||||
|
ps aux | grep php-fpm
|
||||||
|
|
||||||
|
# Disk usage
|
||||||
|
df -h /var/www/html
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 Security Checklist
|
||||||
|
|
||||||
|
After deployment, verify:
|
||||||
|
|
||||||
|
- [ ] ✅ Public site accessible at https://posterg.erg.be/
|
||||||
|
- [ ] ✅ Admin panel requires password
|
||||||
|
- [ ] ✅ Database files return 403 Forbidden
|
||||||
|
- [ ] ✅ Sensitive files (.md, .sql) return 403 Forbidden
|
||||||
|
- [ ] ✅ Shared directory returns 403 Forbidden
|
||||||
|
- [ ] ✅ Security headers present in responses
|
||||||
|
- [ ] ✅ PHP-FPM running and accessible
|
||||||
|
- [ ] ✅ File uploads work in admin panel
|
||||||
|
- [ ] ✅ Search functionality works
|
||||||
|
- [ ] ✅ Logs are being written
|
||||||
|
|
||||||
|
## 🔄 Updating the Site
|
||||||
|
|
||||||
|
For future updates:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy code changes
|
||||||
|
just deploy
|
||||||
|
|
||||||
|
# Reload nginx if config changed
|
||||||
|
ssh posterg "sudo systemctl reload nginx"
|
||||||
|
|
||||||
|
# Clear PHP opcache if needed
|
||||||
|
ssh posterg "sudo systemctl reload php8.4-fpm"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🆘 Emergency Recovery
|
||||||
|
|
||||||
|
If something goes wrong:
|
||||||
|
|
||||||
|
### Restore Default Config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo rm /etc/nginx/sites-enabled/posterg
|
||||||
|
sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reset Permissions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo chown -R www-data:www-data /var/www/html
|
||||||
|
sudo find /var/www/html -type d -exec chmod 755 {} \;
|
||||||
|
sudo find /var/www/html -type f -exec chmod 644 {} \;
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📞 Support Resources
|
||||||
|
|
||||||
|
- **Nginx docs**: https://nginx.org/en/docs/
|
||||||
|
- **PHP-FPM docs**: https://www.php.net/manual/en/install.fpm.php
|
||||||
|
- **Let's Encrypt**: https://letsencrypt.org/
|
||||||
|
- **Security headers**: https://securityheaders.com/
|
||||||
|
|
||||||
|
## 🎉 Success Criteria
|
||||||
|
|
||||||
|
You know the deployment is successful when:
|
||||||
|
|
||||||
|
1. ✅ Visit https://posterg.erg.be/ - shows homepage
|
||||||
|
2. ✅ Visit https://posterg.erg.be/formulaire/ - asks for password
|
||||||
|
3. ✅ Search works correctly
|
||||||
|
4. ✅ Individual thesis pages load
|
||||||
|
5. ✅ Admin can upload files
|
||||||
|
6. ✅ No 403 or 502 errors in logs
|
||||||
|
7. ✅ Security headers present (check with curl -I)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Need help?** Check the error logs first:
|
||||||
|
```bash
|
||||||
|
sudo tail -f /var/log/nginx/posterg_error.log
|
||||||
|
```
|
||||||
+13
-21
@@ -11,36 +11,28 @@ This directory contains nginx configuration and setup scripts for the Post-ERG t
|
|||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
### 1. Set up admin password
|
### 1. Deploy nginx configuration (automated)
|
||||||
|
|
||||||
```bash
|
|
||||||
# Make script executable
|
|
||||||
chmod +x nginx/setup-password.sh
|
|
||||||
|
|
||||||
# Run setup (as root on server)
|
|
||||||
sudo ./nginx/setup-password.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Deploy nginx configuration
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From your local machine
|
# From your local machine
|
||||||
just deploy-nginx
|
just deploy-nginx
|
||||||
|
|
||||||
# Then on the server:
|
# Then on the server:
|
||||||
sudo cp /tmp/posterg.conf /etc/nginx/sites-available/posterg
|
ssh posterg
|
||||||
sudo ln -s /etc/nginx/sites-available/posterg /etc/nginx/sites-enabled/
|
sudo bash /tmp/deploy-production.sh
|
||||||
sudo nginx -t
|
|
||||||
sudo systemctl reload nginx
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Set up SSL (production)
|
The deployment script will:
|
||||||
|
- ✅ Fix file permissions (posterg group)
|
||||||
|
- ✅ Set up admin password (if needed)
|
||||||
|
- ✅ Install nginx configuration
|
||||||
|
- ✅ Test and reload nginx
|
||||||
|
- ✅ Verify PHP-FPM is running
|
||||||
|
|
||||||
```bash
|
### 2. SSL/TLS
|
||||||
# On server
|
|
||||||
sudo apt install certbot python3-certbot-nginx
|
SSL/TLS is handled by the upstream reverse proxy and is already working.
|
||||||
sudo certbot --nginx -d posterg.erg.be -d www.posterg.erg.be
|
No additional SSL setup is needed on this server.
|
||||||
```
|
|
||||||
|
|
||||||
## 🔒 Security Features
|
## 🔒 Security Features
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,352 @@
|
|||||||
|
# Test Database Setup - Post-ERG
|
||||||
|
|
||||||
|
Complete guide for deploying and managing the test database on the production server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Quick Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just test-deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
This automatically:
|
||||||
|
1. ✅ Creates `/var/www/html/database/` directory
|
||||||
|
2. ✅ Uploads `test.db` to the server
|
||||||
|
3. ✅ Sets correct group ownership (`posterg`)
|
||||||
|
4. ✅ Sets correct permissions (775 for dir, 660 for file)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Prerequisites (One-Time Setup)
|
||||||
|
|
||||||
|
### 1. Install PHP SQLite Extension
|
||||||
|
|
||||||
|
On the server:
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo bash /tmp/install-php-sqlite.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install php8.4-sqlite3
|
||||||
|
sudo systemctl restart php8.4-fpm
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Verify Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
php -m | grep sqlite3
|
||||||
|
# Should output: pdo_sqlite, sqlite3
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 How Database Selection Works
|
||||||
|
|
||||||
|
The system automatically detects which database to use:
|
||||||
|
|
||||||
|
1. **If `test.db` exists** → Uses test database
|
||||||
|
2. **Otherwise** → Uses production database (`posterg.db`)
|
||||||
|
|
||||||
|
This is configured in `shared/config.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
function getDatabasePath() {
|
||||||
|
// If test.db exists, use it
|
||||||
|
if (file_exists(DB_TEST_PATH)) {
|
||||||
|
return DB_TEST_PATH;
|
||||||
|
}
|
||||||
|
// Otherwise use production database
|
||||||
|
return DB_PROD_PATH;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Complete Testing Workflow
|
||||||
|
|
||||||
|
### 1. Create Test Data Locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create empty test database from schema
|
||||||
|
just init-test-db
|
||||||
|
|
||||||
|
# Or create with sample fixtures
|
||||||
|
just create-fixtures
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Deploy Test Database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just test-deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Test the Site
|
||||||
|
|
||||||
|
Visit: https://posterg.erg.be/
|
||||||
|
|
||||||
|
The site now uses test data! 🎉
|
||||||
|
|
||||||
|
### 4. Check What Database is Being Used
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
php -r "require_once '/var/www/html/shared/Database.php'; echo 'Using: ' . Database::getInstance()->getDatabasePath() . PHP_EOL;"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output will be:
|
||||||
|
- `/var/www/html/database/test.db` (test mode)
|
||||||
|
- `/var/www/html/database/posterg.db` (production mode)
|
||||||
|
|
||||||
|
### 5. Switch Back to Production
|
||||||
|
|
||||||
|
Simply remove the test database:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
rm /var/www/html/database/test.db
|
||||||
|
```
|
||||||
|
|
||||||
|
The site automatically switches to production database.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 Permissions Explained
|
||||||
|
|
||||||
|
### Directory Permissions
|
||||||
|
|
||||||
|
```
|
||||||
|
drwxrwxr-x theophile posterg /var/www/html/database/
|
||||||
|
```
|
||||||
|
|
||||||
|
- **775**: Owner and group can read/write/execute, others can read/execute
|
||||||
|
- **Group: posterg**: `www-data` is member of this group
|
||||||
|
- **Writable by group**: SQLite needs to create journal/temp files
|
||||||
|
|
||||||
|
### File Permissions
|
||||||
|
|
||||||
|
```
|
||||||
|
-rw-rw---- theophile posterg test.db
|
||||||
|
```
|
||||||
|
|
||||||
|
- **660**: Owner and group can read/write, others have no access
|
||||||
|
- **Group: posterg**: `www-data` can read/write the database
|
||||||
|
- **No public access**: Security - only PHP-FPM can access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Site Shows Empty Page or Error
|
||||||
|
|
||||||
|
**Check error logs:**
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
tail -f /var/log/nginx/posterg_error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### "could not find driver"
|
||||||
|
|
||||||
|
**SQLite extension not installed:**
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sudo apt install php8.4-sqlite3
|
||||||
|
sudo systemctl restart php8.4-fpm
|
||||||
|
```
|
||||||
|
|
||||||
|
### "unable to open database file"
|
||||||
|
|
||||||
|
**Wrong permissions:**
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
# Fix group ownership
|
||||||
|
chgrp posterg /var/www/html/database /var/www/html/database/test.db
|
||||||
|
|
||||||
|
# Fix permissions
|
||||||
|
chmod 775 /var/www/html/database
|
||||||
|
chmod 660 /var/www/html/database/test.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### "SQLSTATE[HY000]: General error: 8 attempt to write a readonly database"
|
||||||
|
|
||||||
|
**Directory not writable:**
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
chmod 775 /var/www/html/database
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Doesn't Update
|
||||||
|
|
||||||
|
**Clear SQLite cache:**
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
rm -f /var/www/html/database/test.db-journal
|
||||||
|
rm -f /var/www/html/database/test.db-shm
|
||||||
|
rm -f /var/www/html/database/test.db-wal
|
||||||
|
```
|
||||||
|
|
||||||
|
Then redeploy:
|
||||||
|
```bash
|
||||||
|
just test-deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Check Database Stats
|
||||||
|
|
||||||
|
### On Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
cd /var/www/html
|
||||||
|
|
||||||
|
# Count theses
|
||||||
|
php -r "require_once 'shared/Database.php'; echo 'Theses: ' . Database::getInstance()->countPublishedTheses() . PHP_EOL;"
|
||||||
|
|
||||||
|
# Check database file
|
||||||
|
ls -lh database/test.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Local Machine
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show stats from local test database
|
||||||
|
sqlite3 database/test.db "SELECT COUNT(*) FROM theses;"
|
||||||
|
sqlite3 database/test.db "SELECT COUNT(*) FROM theses WHERE is_published = 1;"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Update Test Data
|
||||||
|
|
||||||
|
### Update Locally and Redeploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Make changes to local test database
|
||||||
|
sqlite3 database/test.db
|
||||||
|
# ... make changes ...
|
||||||
|
|
||||||
|
# Deploy updated database
|
||||||
|
just test-deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Directly on Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
sqlite3 /var/www/html/database/test.db
|
||||||
|
# ... make changes ...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Important Notes
|
||||||
|
|
||||||
|
### Production Safety
|
||||||
|
|
||||||
|
The `just deploy` command **excludes all `.db` files** by default:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Safe - deploys code only
|
||||||
|
just deploy
|
||||||
|
just deploy-public
|
||||||
|
just deploy-admin
|
||||||
|
|
||||||
|
# Safe - deploys schema/docs only
|
||||||
|
just deploy-database
|
||||||
|
|
||||||
|
# Requires explicit command - deploys test.db
|
||||||
|
just test-deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
This prevents accidentally overwriting production data!
|
||||||
|
|
||||||
|
### Never Commit test.db to Git
|
||||||
|
|
||||||
|
The `.gitignore` already excludes it:
|
||||||
|
|
||||||
|
```
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup Production Database
|
||||||
|
|
||||||
|
Before deploying test database, backup production if needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh posterg
|
||||||
|
cp /var/www/html/database/posterg.db /var/www/html/database/posterg.db.backup.$(date +%Y%m%d)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 Database File Locations
|
||||||
|
|
||||||
|
### Local (Development)
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/theophile/dev/posterg-website/
|
||||||
|
└── database/
|
||||||
|
├── schema.sql # Database schema
|
||||||
|
├── test.db # Test database (gitignored)
|
||||||
|
└── fixtures/ # Test data generators
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server (Production)
|
||||||
|
|
||||||
|
```
|
||||||
|
/var/www/html/
|
||||||
|
└── database/
|
||||||
|
├── posterg.db # Production database
|
||||||
|
└── test.db # Test database (if deployed)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Related Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `just init-test-db` | Create empty test database |
|
||||||
|
| `just create-fixtures` | Create test database with sample data |
|
||||||
|
| `just test-deploy` | Deploy test database to server |
|
||||||
|
| `just stats-public` | Show local database statistics |
|
||||||
|
| `just query-db` | Open SQLite prompt for local test.db |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Deployment Checklist
|
||||||
|
|
||||||
|
After running `just test-deploy`, verify:
|
||||||
|
|
||||||
|
- [ ] Database file exists: `ssh posterg "ls -la /var/www/html/database/test.db"`
|
||||||
|
- [ ] Correct permissions: `-rw-rw---- theophile posterg`
|
||||||
|
- [ ] Directory writable: `drwxrwxr-x theophile posterg`
|
||||||
|
- [ ] Site loads: Visit https://posterg.erg.be/
|
||||||
|
- [ ] No errors in logs: `ssh posterg "tail /var/log/nginx/posterg_error.log"`
|
||||||
|
- [ ] Database accessible: Test with admin panel
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 Success!
|
||||||
|
|
||||||
|
When working correctly:
|
||||||
|
|
||||||
|
- ✅ Main page shows test data
|
||||||
|
- ✅ Search works with test data
|
||||||
|
- ✅ Admin panel loads form
|
||||||
|
- ✅ No database errors in logs
|
||||||
|
- ✅ Can create/edit/delete test entries
|
||||||
|
|
||||||
|
To switch back to production, just:
|
||||||
|
```bash
|
||||||
|
ssh posterg "rm /var/www/html/database/test.db"
|
||||||
|
```
|
||||||
|
|
||||||
|
Site automatically uses `posterg.db` again! 🚀
|
||||||
Executable
+180
@@ -0,0 +1,180 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy production nginx configuration and fix permissions for Post-ERG
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Post-ERG Production Deployment"
|
||||||
|
echo "=================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo -e "${RED}Error: This script must be run as root (use sudo)${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "📋 Step 1: Fixing file permissions..."
|
||||||
|
echo "--------------------------------------"
|
||||||
|
|
||||||
|
# Change group to posterg (www-data is member of this group)
|
||||||
|
chown -R theophile:posterg /var/www/html/
|
||||||
|
echo "✓ Changed group to posterg"
|
||||||
|
|
||||||
|
# Set directory permissions (755 - readable/executable by everyone)
|
||||||
|
find /var/www/html -type d -exec chmod 755 {} \;
|
||||||
|
echo "✓ Set directory permissions to 755"
|
||||||
|
|
||||||
|
# Set file permissions (640 - owner read/write, group read)
|
||||||
|
find /var/www/html -type f -exec chmod 640 {} \;
|
||||||
|
echo "✓ Set file permissions to 640"
|
||||||
|
|
||||||
|
# Make upload directories writable by group (for www-data to write)
|
||||||
|
if [ -d "/var/www/html/formulaire/data/theses" ]; then
|
||||||
|
chmod 775 /var/www/html/formulaire/data/theses
|
||||||
|
chmod 775 /var/www/html/formulaire/data/covers
|
||||||
|
echo "✓ Set upload directories to 775"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Protect database if it exists
|
||||||
|
if [ -f "/var/www/html/database/posterg.db" ]; then
|
||||||
|
chmod 660 /var/www/html/database/posterg.db
|
||||||
|
chown www-data:posterg /var/www/html/database/posterg.db
|
||||||
|
echo "✓ Protected database file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📋 Step 2: Checking prerequisites..."
|
||||||
|
echo "--------------------------------------"
|
||||||
|
|
||||||
|
# Check if htpasswd is available
|
||||||
|
if ! command -v htpasswd &> /dev/null; then
|
||||||
|
echo -e "${YELLOW}⚠️ htpasswd not found, installing apache2-utils...${NC}"
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y apache2-utils
|
||||||
|
echo -e "${GREEN}✓ apache2-utils installed${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if htpasswd file exists
|
||||||
|
if [ ! -f "/etc/nginx/.htpasswd-posterg" ]; then
|
||||||
|
echo -e "${YELLOW}⚠️ Warning: /etc/nginx/.htpasswd-posterg not found${NC}"
|
||||||
|
echo " Creating it now..."
|
||||||
|
echo ""
|
||||||
|
echo "Please enter admin username:"
|
||||||
|
read -r ADMIN_USER
|
||||||
|
htpasswd -c /etc/nginx/.htpasswd-posterg "$ADMIN_USER"
|
||||||
|
echo -e "${GREEN}✓ Password file created${NC}"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo "✓ Password file exists"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if config file was uploaded
|
||||||
|
if [ ! -f "/tmp/posterg.conf" ]; then
|
||||||
|
echo -e "${RED}✗ Error: /tmp/posterg.conf not found${NC}"
|
||||||
|
echo "Please upload it first: rsync -vur ./nginx/posterg-production.conf posterg:/tmp/posterg.conf"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📋 Step 3: Installing nginx configuration..."
|
||||||
|
echo "--------------------------------------"
|
||||||
|
|
||||||
|
# Backup existing config if it exists
|
||||||
|
if [ -f "/etc/nginx/sites-available/posterg" ]; then
|
||||||
|
cp /etc/nginx/sites-available/posterg /etc/nginx/sites-available/posterg.backup.$(date +%Y%m%d_%H%M%S)
|
||||||
|
echo "✓ Backed up existing config"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy new configuration
|
||||||
|
cp /tmp/posterg.conf /etc/nginx/sites-available/posterg
|
||||||
|
echo "✓ Installed configuration to /etc/nginx/sites-available/posterg"
|
||||||
|
|
||||||
|
# Create symlink
|
||||||
|
if [ ! -L "/etc/nginx/sites-enabled/posterg" ]; then
|
||||||
|
ln -s /etc/nginx/sites-available/posterg /etc/nginx/sites-enabled/posterg
|
||||||
|
echo "✓ Created symlink in sites-enabled"
|
||||||
|
else
|
||||||
|
echo "✓ Symlink already exists"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove default site
|
||||||
|
if [ -L "/etc/nginx/sites-enabled/default" ]; then
|
||||||
|
rm /etc/nginx/sites-enabled/default
|
||||||
|
echo "✓ Disabled default site"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📋 Step 4: Testing nginx configuration..."
|
||||||
|
echo "--------------------------------------"
|
||||||
|
|
||||||
|
if nginx -t; then
|
||||||
|
echo -e "${GREEN}✓ Nginx configuration is valid${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Nginx configuration has errors!${NC}"
|
||||||
|
echo "Restoring backup..."
|
||||||
|
if ls /etc/nginx/sites-available/posterg.backup* 1> /dev/null 2>&1; then
|
||||||
|
BACKUP=$(ls -t /etc/nginx/sites-available/posterg.backup* | head -1)
|
||||||
|
cp "$BACKUP" /etc/nginx/sites-available/posterg
|
||||||
|
echo "Configuration restored from backup"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📋 Step 5: Reloading nginx..."
|
||||||
|
echo "--------------------------------------"
|
||||||
|
|
||||||
|
if systemctl reload nginx; then
|
||||||
|
echo -e "${GREEN}✓ Nginx reloaded successfully${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Failed to reload nginx${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📋 Step 6: Verifying services..."
|
||||||
|
echo "--------------------------------------"
|
||||||
|
|
||||||
|
# Check PHP-FPM
|
||||||
|
if systemctl is-active --quiet php8.4-fpm; then
|
||||||
|
echo -e "${GREEN}✓ PHP 8.4-FPM is running${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠️ PHP-FPM is not running, starting it...${NC}"
|
||||||
|
systemctl start php8.4-fpm
|
||||||
|
systemctl enable php8.4-fpm
|
||||||
|
echo -e "${GREEN}✓ PHP-FPM started${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check nginx
|
||||||
|
if systemctl is-active --quiet nginx; then
|
||||||
|
echo -e "${GREEN}✓ Nginx is running${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Nginx is not running!${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "═══════════════════════════════════════"
|
||||||
|
echo -e "${GREEN}✅ Deployment Complete!${NC}"
|
||||||
|
echo "═══════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
echo "🧪 Quick Tests:"
|
||||||
|
echo " • Test public site: curl -I http://localhost/"
|
||||||
|
echo " • Test admin panel: curl -I http://localhost/formulaire/"
|
||||||
|
echo " • Test PHP: curl http://localhost/index.php"
|
||||||
|
echo ""
|
||||||
|
echo "📊 View logs:"
|
||||||
|
echo " • Access log: tail -f /var/log/nginx/posterg_access.log"
|
||||||
|
echo " • Error log: tail -f /var/log/nginx/posterg_error.log"
|
||||||
|
echo ""
|
||||||
|
echo "🔒 Security Checks:"
|
||||||
|
echo " • Database blocked: curl -I http://localhost/database/posterg.db"
|
||||||
|
echo " • MD files blocked: curl -I http://localhost/README.md"
|
||||||
|
echo " • Shared blocked: curl -I http://localhost/shared/Database.php"
|
||||||
|
echo ""
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Fix shared library paths for production deployment
|
||||||
|
|
||||||
|
echo "🔧 Fixing shared library paths for production..."
|
||||||
|
|
||||||
|
cd /var/www/html
|
||||||
|
|
||||||
|
# Fix paths in PHP files
|
||||||
|
find . -maxdepth 1 -name "*.php" -type f -exec sed -i "s|__DIR__ \. '/\.\./\.\./shared/|__DIR__ . '/shared/|g" {} \;
|
||||||
|
|
||||||
|
echo "✓ Updated paths in:"
|
||||||
|
echo " - index.php"
|
||||||
|
echo " - memoire.php"
|
||||||
|
echo " - search.php"
|
||||||
|
echo " - test_db.php"
|
||||||
|
|
||||||
|
# Test if it works
|
||||||
|
echo ""
|
||||||
|
echo "🧪 Testing..."
|
||||||
|
php -r "require_once '/var/www/html/shared/Database.php'; echo 'Database.php loads successfully\n';"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Path fix complete!"
|
||||||
|
echo "Try: curl http://localhost/"
|
||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Install PHP SQLite extension
|
||||||
|
|
||||||
|
echo "🔧 Installing PHP SQLite extension..."
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Error: This script must be run as root (use sudo)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect PHP version
|
||||||
|
PHP_VERSION=$(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;")
|
||||||
|
echo "Detected PHP version: $PHP_VERSION"
|
||||||
|
|
||||||
|
# Install SQLite extension
|
||||||
|
echo "Installing php${PHP_VERSION}-sqlite3..."
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y php${PHP_VERSION}-sqlite3
|
||||||
|
|
||||||
|
# Restart PHP-FPM
|
||||||
|
echo "Restarting PHP-FPM..."
|
||||||
|
systemctl restart php${PHP_VERSION}-fpm
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
if php -m | grep -q sqlite3; then
|
||||||
|
echo "✅ SQLite extension installed successfully"
|
||||||
|
echo ""
|
||||||
|
echo "Installed extensions:"
|
||||||
|
php -m | grep -i sqlite
|
||||||
|
else
|
||||||
|
echo "❌ Failed to install SQLite extension"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Executable
+199
@@ -0,0 +1,199 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Manage admin users for Post-ERG nginx basic authentication
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
PASSWORD_FILE="/etc/nginx/.htpasswd-posterg"
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo -e "${RED}Error: This script must be run as root (use sudo)${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if htpasswd is available
|
||||||
|
if ! command -v htpasswd &> /dev/null; then
|
||||||
|
echo -e "${YELLOW}Installing apache2-utils...${NC}"
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y apache2-utils
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_menu() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${BLUE} Post-ERG Admin User Management${NC}"
|
||||||
|
echo -e "${BLUE}════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "1. List all users"
|
||||||
|
echo "2. Add new user"
|
||||||
|
echo "3. Change user password"
|
||||||
|
echo "4. Delete user"
|
||||||
|
echo "5. Reset all (create new password file)"
|
||||||
|
echo "6. Exit"
|
||||||
|
echo ""
|
||||||
|
echo -n "Choose an option [1-6]: "
|
||||||
|
}
|
||||||
|
|
||||||
|
list_users() {
|
||||||
|
echo ""
|
||||||
|
if [ ! -f "$PASSWORD_FILE" ]; then
|
||||||
|
echo -e "${YELLOW}No password file found.${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Current admin users:${NC}"
|
||||||
|
echo "────────────────────────"
|
||||||
|
cut -d: -f1 "$PASSWORD_FILE" | nl
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
add_user() {
|
||||||
|
echo ""
|
||||||
|
echo -n "Enter new username: "
|
||||||
|
read -r USERNAME
|
||||||
|
|
||||||
|
if [ -z "$USERNAME" ]; then
|
||||||
|
echo -e "${RED}Username cannot be empty${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if user already exists
|
||||||
|
if [ -f "$PASSWORD_FILE" ] && grep -q "^${USERNAME}:" "$PASSWORD_FILE"; then
|
||||||
|
echo -e "${YELLOW}User '$USERNAME' already exists. Use option 3 to change password.${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add user (use -c only if file doesn't exist)
|
||||||
|
if [ ! -f "$PASSWORD_FILE" ]; then
|
||||||
|
htpasswd -c "$PASSWORD_FILE" "$USERNAME"
|
||||||
|
else
|
||||||
|
htpasswd "$PASSWORD_FILE" "$USERNAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ User '$USERNAME' added successfully${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
change_password() {
|
||||||
|
list_users
|
||||||
|
echo -n "Enter username to change password: "
|
||||||
|
read -r USERNAME
|
||||||
|
|
||||||
|
if [ -z "$USERNAME" ]; then
|
||||||
|
echo -e "${RED}Username cannot be empty${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$PASSWORD_FILE" ]; then
|
||||||
|
echo -e "${RED}Password file not found${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "^${USERNAME}:" "$PASSWORD_FILE"; then
|
||||||
|
echo -e "${RED}User '$USERNAME' not found${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
htpasswd "$PASSWORD_FILE" "$USERNAME"
|
||||||
|
echo -e "${GREEN}✓ Password changed for user '$USERNAME'${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_user() {
|
||||||
|
list_users
|
||||||
|
echo -n "Enter username to delete: "
|
||||||
|
read -r USERNAME
|
||||||
|
|
||||||
|
if [ -z "$USERNAME" ]; then
|
||||||
|
echo -e "${RED}Username cannot be empty${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$PASSWORD_FILE" ]; then
|
||||||
|
echo -e "${RED}Password file not found${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "^${USERNAME}:" "$PASSWORD_FILE"; then
|
||||||
|
echo -e "${RED}User '$USERNAME' not found${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "Are you sure you want to delete user '$USERNAME'? [y/N] "
|
||||||
|
read -r CONFIRM
|
||||||
|
|
||||||
|
if [ "$CONFIRM" = "y" ] || [ "$CONFIRM" = "Y" ]; then
|
||||||
|
htpasswd -D "$PASSWORD_FILE" "$USERNAME"
|
||||||
|
echo -e "${GREEN}✓ User '$USERNAME' deleted${NC}"
|
||||||
|
else
|
||||||
|
echo "Cancelled"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_all() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}WARNING: This will delete ALL existing users!${NC}"
|
||||||
|
echo -n "Are you sure? [y/N] "
|
||||||
|
read -r CONFIRM
|
||||||
|
|
||||||
|
if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
|
||||||
|
echo "Cancelled"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Backup existing file
|
||||||
|
if [ -f "$PASSWORD_FILE" ]; then
|
||||||
|
BACKUP="${PASSWORD_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
cp "$PASSWORD_FILE" "$BACKUP"
|
||||||
|
echo -e "${GREEN}✓ Backed up to: $BACKUP${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -n "Enter new username: "
|
||||||
|
read -r USERNAME
|
||||||
|
|
||||||
|
if [ -z "$USERNAME" ]; then
|
||||||
|
echo -e "${RED}Username cannot be empty${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
htpasswd -c "$PASSWORD_FILE" "$USERNAME"
|
||||||
|
echo -e "${GREEN}✓ Password file reset with user '$USERNAME'${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main loop
|
||||||
|
while true; do
|
||||||
|
show_menu
|
||||||
|
read -r CHOICE
|
||||||
|
|
||||||
|
case $CHOICE in
|
||||||
|
1)
|
||||||
|
list_users
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
add_user
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
change_password
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
delete_user
|
||||||
|
;;
|
||||||
|
5)
|
||||||
|
reset_all
|
||||||
|
;;
|
||||||
|
6)
|
||||||
|
echo ""
|
||||||
|
echo "Goodbye!"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Invalid option${NC}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
+49
-192
@@ -1,24 +1,23 @@
|
|||||||
# Nginx configuration for Post-ERG thesis website
|
# Nginx configuration for Post-ERG thesis website (Production)
|
||||||
|
# Based on existing default config with security enhancements
|
||||||
# Place this in /etc/nginx/sites-available/posterg
|
# Place this in /etc/nginx/sites-available/posterg
|
||||||
# Then symlink: ln -s /etc/nginx/sites-available/posterg /etc/nginx/sites-enabled/
|
|
||||||
|
|
||||||
# Rate limiting zones
|
# Rate limiting zones
|
||||||
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/m;
|
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/m;
|
||||||
limit_req_zone $binary_remote_addr zone=search:10m rate=30r/m;
|
limit_req_zone $binary_remote_addr zone=search:10m rate=30r/m;
|
||||||
limit_req_zone $binary_remote_addr zone=admin:10m rate=10r/m;
|
limit_req_zone $binary_remote_addr zone=admin:10m rate=10r/m;
|
||||||
|
|
||||||
# Server block - HTTP (redirect to HTTPS in production)
|
# Main server block
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80 default_server;
|
||||||
listen [::]:80;
|
listen [::]:80 default_server;
|
||||||
|
|
||||||
server_name posterg.erg.be www.posterg.erg.be;
|
server_name posterg.erg.be www.posterg.erg.be;
|
||||||
|
|
||||||
# Redirect all HTTP to HTTPS (uncomment in production)
|
|
||||||
# return 301 https://$server_name$request_uri;
|
|
||||||
|
|
||||||
# For development/testing, allow HTTP
|
|
||||||
root /var/www/html;
|
root /var/www/html;
|
||||||
index index.php index.html;
|
|
||||||
|
# Add index.php to the list
|
||||||
|
index index.php index.html index.htm;
|
||||||
|
|
||||||
# Security headers
|
# Security headers
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
@@ -27,8 +26,8 @@ server {
|
|||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||||||
|
|
||||||
# Disable server tokens
|
# Server tokens already disabled in nginx.conf
|
||||||
server_tokens off;
|
# server_tokens off;
|
||||||
|
|
||||||
# Max upload size (for thesis files)
|
# Max upload size (for thesis files)
|
||||||
client_max_body_size 100M;
|
client_max_body_size 100M;
|
||||||
@@ -38,48 +37,35 @@ server {
|
|||||||
access_log /var/log/nginx/posterg_access.log;
|
access_log /var/log/nginx/posterg_access.log;
|
||||||
error_log /var/log/nginx/posterg_error.log warn;
|
error_log /var/log/nginx/posterg_error.log warn;
|
||||||
|
|
||||||
# Block common attack patterns
|
# Block access to hidden files (except .well-known for Let's Encrypt)
|
||||||
location ~ /\. {
|
location ~ /\.(?!well-known).* {
|
||||||
deny all;
|
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ \.(git|env|db-journal)$ {
|
|
||||||
deny all;
|
deny all;
|
||||||
access_log off;
|
access_log off;
|
||||||
log_not_found off;
|
log_not_found off;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Deny access to sensitive files
|
# Deny access to sensitive files
|
||||||
location ~* \.(md|txt|sql|sh|json)$ {
|
location ~* \.(md|txt|sql|sh|json|gitignore)$ {
|
||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Deny access to database files
|
# Deny access to database directory
|
||||||
location ~* \.db$ {
|
location ^~ /database/ {
|
||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Deny access to shared/ directory (PHP includes only)
|
# Deny access to shared/ directory (PHP includes only)
|
||||||
location /shared/ {
|
location ^~ /shared/ {
|
||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Deny access to tests directory
|
# Deny access to data directory
|
||||||
location /tests/ {
|
location ^~ /data/ {
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deny access to cache directory
|
|
||||||
location /cache/ {
|
|
||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Admin panel - password protected
|
# Admin panel - password protected
|
||||||
location /formulaire/ {
|
location ^~ /formulaire/ {
|
||||||
alias /var/www/html/formulaire/;
|
|
||||||
|
|
||||||
# HTTP Basic Authentication
|
# HTTP Basic Authentication
|
||||||
auth_basic "Admin Access - Post-ERG";
|
auth_basic "Admin Access - Post-ERG";
|
||||||
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
|
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
|
||||||
@@ -87,35 +73,42 @@ server {
|
|||||||
# Rate limiting for admin
|
# Rate limiting for admin
|
||||||
limit_req zone=admin burst=5 nodelay;
|
limit_req zone=admin burst=5 nodelay;
|
||||||
|
|
||||||
# PHP handling
|
# Allow access to public assets without authentication
|
||||||
location ~ \.php$ {
|
location ~ ^/formulaire/(css|js|images)/ {
|
||||||
include snippets/fastcgi-php.conf;
|
auth_basic off;
|
||||||
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
|
||||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Additional security for admin
|
# PHP handling for admin
|
||||||
|
location ~ \.php$ {
|
||||||
|
include snippets/fastcgi-php.conf;
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Additional security headers for admin
|
||||||
add_header X-Robots-Tag "noindex, nofollow" always;
|
add_header X-Robots-Tag "noindex, nofollow" always;
|
||||||
|
|
||||||
|
# Try to serve file, otherwise 404
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Search endpoint - rate limiting
|
# Search endpoint - rate limiting
|
||||||
location /search.php {
|
location = /search.php {
|
||||||
limit_req zone=search burst=10 nodelay;
|
limit_req zone=search burst=10 nodelay;
|
||||||
include snippets/fastcgi-php.conf;
|
include snippets/fastcgi-php.conf;
|
||||||
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Public PHP files
|
# PHP files handler
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
|
# Rate limiting for general PHP requests
|
||||||
limit_req zone=general burst=20 nodelay;
|
limit_req zone=general burst=20 nodelay;
|
||||||
|
|
||||||
include snippets/fastcgi-php.conf;
|
include snippets/fastcgi-php.conf;
|
||||||
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
|
||||||
|
|
||||||
# Security parameters
|
# Security parameters
|
||||||
fastcgi_param PHP_VALUE "upload_max_filesize=50M \n post_max_size=100M";
|
fastcgi_param PHP_VALUE "upload_max_filesize=50M \n post_max_size=100M";
|
||||||
fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/html:/tmp";
|
|
||||||
|
|
||||||
# Timeouts
|
# Timeouts
|
||||||
fastcgi_read_timeout 120;
|
fastcgi_read_timeout 120;
|
||||||
fastcgi_send_timeout 120;
|
fastcgi_send_timeout 120;
|
||||||
@@ -128,156 +121,20 @@ server {
|
|||||||
access_log off;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Root location
|
# PDF files (thesis documents)
|
||||||
|
location ~* \.pdf$ {
|
||||||
|
expires 7d;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
add_header Content-Disposition "inline";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Root location - try files or 404
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ =404;
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Deny access to specific file types in data directories
|
# Deny access to .htaccess files (if Apache's document root concurs with nginx's)
|
||||||
location ~* /data/.*\.(php|sh|py)$ {
|
location ~ /\.ht {
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Server block - HTTPS (production)
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
listen [::]:443 ssl http2;
|
|
||||||
server_name posterg.erg.be www.posterg.erg.be;
|
|
||||||
|
|
||||||
root /var/www/html;
|
|
||||||
index index.php index.html;
|
|
||||||
|
|
||||||
# SSL certificates (Let's Encrypt)
|
|
||||||
# Run: certbot --nginx -d posterg.erg.be -d www.posterg.erg.be
|
|
||||||
ssl_certificate /etc/letsencrypt/live/posterg.erg.be/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/posterg.erg.be/privkey.pem;
|
|
||||||
|
|
||||||
# SSL configuration
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
|
|
||||||
ssl_prefer_server_ciphers off;
|
|
||||||
ssl_session_cache shared:SSL:10m;
|
|
||||||
ssl_session_timeout 10m;
|
|
||||||
ssl_stapling on;
|
|
||||||
ssl_stapling_verify on;
|
|
||||||
|
|
||||||
# Security headers (HTTPS)
|
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
||||||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
|
||||||
|
|
||||||
# Disable server tokens
|
|
||||||
server_tokens off;
|
|
||||||
|
|
||||||
# Max upload size
|
|
||||||
client_max_body_size 100M;
|
|
||||||
client_body_timeout 120s;
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
access_log /var/log/nginx/posterg_ssl_access.log;
|
|
||||||
error_log /var/log/nginx/posterg_ssl_error.log warn;
|
|
||||||
|
|
||||||
# Block common attack patterns
|
|
||||||
location ~ /\. {
|
|
||||||
deny all;
|
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ \.(git|env|db-journal)$ {
|
|
||||||
deny all;
|
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deny access to sensitive files
|
|
||||||
location ~* \.(md|txt|sql|sh|json)$ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deny access to database files
|
|
||||||
location ~* \.db$ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deny access to shared/ directory
|
|
||||||
location /shared/ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deny access to tests directory
|
|
||||||
location /tests/ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deny access to cache directory
|
|
||||||
location /cache/ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Admin panel - password protected
|
|
||||||
location /formulaire/ {
|
|
||||||
alias /var/www/html/formulaire/;
|
|
||||||
|
|
||||||
# HTTP Basic Authentication
|
|
||||||
auth_basic "Admin Access - Post-ERG";
|
|
||||||
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
|
|
||||||
|
|
||||||
# Rate limiting
|
|
||||||
limit_req zone=admin burst=5 nodelay;
|
|
||||||
|
|
||||||
# PHP handling
|
|
||||||
location ~ \.php$ {
|
|
||||||
include snippets/fastcgi-php.conf;
|
|
||||||
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
|
||||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Security headers
|
|
||||||
add_header X-Robots-Tag "noindex, nofollow" always;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Search endpoint - rate limiting
|
|
||||||
location /search.php {
|
|
||||||
limit_req zone=search burst=10 nodelay;
|
|
||||||
include snippets/fastcgi-php.conf;
|
|
||||||
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Public PHP files
|
|
||||||
location ~ \.php$ {
|
|
||||||
limit_req zone=general burst=20 nodelay;
|
|
||||||
|
|
||||||
include snippets/fastcgi-php.conf;
|
|
||||||
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
|
||||||
|
|
||||||
# Security parameters
|
|
||||||
fastcgi_param PHP_VALUE "upload_max_filesize=50M \n post_max_size=100M";
|
|
||||||
fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/html:/tmp";
|
|
||||||
|
|
||||||
# Timeouts
|
|
||||||
fastcgi_read_timeout 120;
|
|
||||||
fastcgi_send_timeout 120;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Static files caching
|
|
||||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|otf)$ {
|
|
||||||
expires 30d;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
access_log off;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Root location
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deny access to script files in data directories
|
|
||||||
location ~* /data/.*\.(php|sh|py)$ {
|
|
||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
# Nginx configuration for Post-ERG thesis website
|
||||||
|
# Place this in /etc/nginx/sites-available/posterg
|
||||||
|
# Then symlink: ln -s /etc/nginx/sites-available/posterg /etc/nginx/sites-enabled/
|
||||||
|
|
||||||
|
# Rate limiting zones
|
||||||
|
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/m;
|
||||||
|
limit_req_zone $binary_remote_addr zone=search:10m rate=30r/m;
|
||||||
|
limit_req_zone $binary_remote_addr zone=admin:10m rate=10r/m;
|
||||||
|
|
||||||
|
# Server block - HTTP (redirect to HTTPS in production)
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name posterg.erg.be www.posterg.erg.be;
|
||||||
|
|
||||||
|
# Redirect all HTTP to HTTPS (uncomment in production)
|
||||||
|
# return 301 https://$server_name$request_uri;
|
||||||
|
|
||||||
|
# For development/testing, allow HTTP
|
||||||
|
root /var/www/html;
|
||||||
|
index index.php index.html;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||||||
|
|
||||||
|
# Disable server tokens
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
# Max upload size (for thesis files)
|
||||||
|
client_max_body_size 100M;
|
||||||
|
client_body_timeout 120s;
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
access_log /var/log/nginx/posterg_access.log;
|
||||||
|
error_log /var/log/nginx/posterg_error.log warn;
|
||||||
|
|
||||||
|
# Block common attack patterns
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.(git|env|db-journal)$ {
|
||||||
|
deny all;
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to sensitive files
|
||||||
|
location ~* \.(md|txt|sql|sh|json)$ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to database files
|
||||||
|
location ~* \.db$ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to shared/ directory (PHP includes only)
|
||||||
|
location /shared/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to tests directory
|
||||||
|
location /tests/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to cache directory
|
||||||
|
location /cache/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Admin panel - password protected
|
||||||
|
location /formulaire/ {
|
||||||
|
alias /var/www/html/formulaire/;
|
||||||
|
|
||||||
|
# HTTP Basic Authentication
|
||||||
|
auth_basic "Admin Access - Post-ERG";
|
||||||
|
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
|
||||||
|
|
||||||
|
# Rate limiting for admin
|
||||||
|
limit_req zone=admin burst=5 nodelay;
|
||||||
|
|
||||||
|
# PHP handling
|
||||||
|
location ~ \.php$ {
|
||||||
|
include snippets/fastcgi-php.conf;
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Additional security for admin
|
||||||
|
add_header X-Robots-Tag "noindex, nofollow" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Search endpoint - rate limiting
|
||||||
|
location /search.php {
|
||||||
|
limit_req zone=search burst=10 nodelay;
|
||||||
|
include snippets/fastcgi-php.conf;
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Public PHP files
|
||||||
|
location ~ \.php$ {
|
||||||
|
limit_req zone=general burst=20 nodelay;
|
||||||
|
|
||||||
|
include snippets/fastcgi-php.conf;
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
||||||
|
|
||||||
|
# Security parameters
|
||||||
|
fastcgi_param PHP_VALUE "upload_max_filesize=50M \n post_max_size=100M";
|
||||||
|
fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/html:/tmp";
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
fastcgi_read_timeout 120;
|
||||||
|
fastcgi_send_timeout 120;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static files caching
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|otf)$ {
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Root location
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to specific file types in data directories
|
||||||
|
location ~* /data/.*\.(php|sh|py)$ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Server block - HTTPS (production)
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name posterg.erg.be www.posterg.erg.be;
|
||||||
|
|
||||||
|
root /var/www/html;
|
||||||
|
index index.php index.html;
|
||||||
|
|
||||||
|
# SSL certificates (Let's Encrypt)
|
||||||
|
# Run: certbot --nginx -d posterg.erg.be -d www.posterg.erg.be
|
||||||
|
ssl_certificate /etc/letsencrypt/live/posterg.erg.be/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/posterg.erg.be/privkey.pem;
|
||||||
|
|
||||||
|
# SSL configuration
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
|
||||||
|
ssl_prefer_server_ciphers off;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 10m;
|
||||||
|
ssl_stapling on;
|
||||||
|
ssl_stapling_verify on;
|
||||||
|
|
||||||
|
# Security headers (HTTPS)
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||||||
|
|
||||||
|
# Disable server tokens
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
# Max upload size
|
||||||
|
client_max_body_size 100M;
|
||||||
|
client_body_timeout 120s;
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
access_log /var/log/nginx/posterg_ssl_access.log;
|
||||||
|
error_log /var/log/nginx/posterg_ssl_error.log warn;
|
||||||
|
|
||||||
|
# Block common attack patterns
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.(git|env|db-journal)$ {
|
||||||
|
deny all;
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to sensitive files
|
||||||
|
location ~* \.(md|txt|sql|sh|json)$ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to database files
|
||||||
|
location ~* \.db$ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to shared/ directory
|
||||||
|
location /shared/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to tests directory
|
||||||
|
location /tests/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to cache directory
|
||||||
|
location /cache/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Admin panel - password protected
|
||||||
|
location /formulaire/ {
|
||||||
|
alias /var/www/html/formulaire/;
|
||||||
|
|
||||||
|
# HTTP Basic Authentication
|
||||||
|
auth_basic "Admin Access - Post-ERG";
|
||||||
|
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
limit_req zone=admin burst=5 nodelay;
|
||||||
|
|
||||||
|
# PHP handling
|
||||||
|
location ~ \.php$ {
|
||||||
|
include snippets/fastcgi-php.conf;
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Robots-Tag "noindex, nofollow" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Search endpoint - rate limiting
|
||||||
|
location /search.php {
|
||||||
|
limit_req zone=search burst=10 nodelay;
|
||||||
|
include snippets/fastcgi-php.conf;
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Public PHP files
|
||||||
|
location ~ \.php$ {
|
||||||
|
limit_req zone=general burst=20 nodelay;
|
||||||
|
|
||||||
|
include snippets/fastcgi-php.conf;
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
||||||
|
|
||||||
|
# Security parameters
|
||||||
|
fastcgi_param PHP_VALUE "upload_max_filesize=50M \n post_max_size=100M";
|
||||||
|
fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/html:/tmp";
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
fastcgi_read_timeout 120;
|
||||||
|
fastcgi_send_timeout 120;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static files caching
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|otf)$ {
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Root location
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to script files in data directories
|
||||||
|
location ~* /data/.*\.(php|sh|py)$ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
[1769624735]
|
[1770299235]
|
||||||
Reference in new issue
Block a user