mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
Nginx config, working deploy, basic theme, repo cleanup
This commit is contained in:
@@ -1,342 +1,559 @@
|
|||||||
@font-face {
|
/* ============================================
|
||||||
font-family: police1;
|
POST-ERG - Minimalistic CSS
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Custom Font */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Combined';
|
||||||
src: url("fonts/Combinedd.otf");
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
CARDS
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--color-bg);
|
||||||
|
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 {
|
.navbar {
|
||||||
font-family: 'police1';
|
padding: 1.5rem 1rem;
|
||||||
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 {
|
.navbar-brand h1 {
|
||||||
text-decoration: none;
|
font-size: 1.8rem;
|
||||||
color: white;
|
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar a:hover {
|
.navbar-menu {
|
||||||
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 */
|
|
||||||
/* .button {
|
|
||||||
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 {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 2rem;
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.memoire img {
|
.title.is-4 {
|
||||||
max-width: 40%;
|
font-size: 1.2rem;
|
||||||
margin: 0.5rem;
|
}
|
||||||
} */
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.columns {
|
||||||
|
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>
|
||||||
|
|||||||
887
database/DATABASE_SPECIFICATION.md
Normal file
887
database/DATABASE_SPECIFICATION.md
Normal file
@@ -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.
206
database/QUICK_SCHEMA_REFERENCE.md
Normal file
206
database/QUICK_SCHEMA_REFERENCE.md
Normal file
@@ -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.
|
||||||
@@ -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
|
|
||||||
|
|||||||
BIN
database/test.db
BIN
database/test.db
Binary file not shown.
269
docs/CSS_CLEANUP.md
Normal file
269
docs/CSS_CLEANUP.md
Normal file
@@ -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! 🎉
|
||||||
47
justfile
47
justfile
@@ -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:
|
||||||
|
|||||||
271
nginx/ADMIN_USERS.md
Normal file
271
nginx/ADMIN_USERS.md
Normal file
@@ -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! 🔐
|
||||||
379
nginx/DEPLOYMENT_COMPLETE.md
Normal file
379
nginx/DEPLOYMENT_COMPLETE.md
Normal file
@@ -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** 🎉
|
||||||
276
nginx/DEPLOY_NOW.md
Normal file
276
nginx/DEPLOY_NOW.md
Normal file
@@ -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
|
||||||
|
```
|
||||||
346
nginx/PRODUCTION_DEPLOYMENT.md
Normal file
346
nginx/PRODUCTION_DEPLOYMENT.md
Normal file
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
352
nginx/TEST_DATABASE_SETUP.md
Normal file
352
nginx/TEST_DATABASE_SETUP.md
Normal file
@@ -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! 🚀
|
||||||
180
nginx/deploy-production.sh
Executable file
180
nginx/deploy-production.sh
Executable file
@@ -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 ""
|
||||||
24
nginx/fix-paths.sh
Normal file
24
nginx/fix-paths.sh
Normal file
@@ -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/"
|
||||||
34
nginx/install-php-sqlite.sh
Executable file
34
nginx/install-php-sqlite.sh
Executable file
@@ -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
|
||||||
199
nginx/manage-admin-users.sh
Executable file
199
nginx/manage-admin-users.sh
Executable file
@@ -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
|
||||||
@@ -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,34 +73,41 @@ 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;
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
283
nginx/posterg.conf.reference
Normal file
283
nginx/posterg.conf.reference
Normal file
@@ -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