Docfile commited on
Commit
6699502
·
verified ·
1 Parent(s): c48f4d2

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +17 -34
  2. app.py +349 -4
  3. main.py +9 -9
Dockerfile CHANGED
@@ -1,34 +1,17 @@
1
- # Lire la documentation : https://huggingface.co/docs/hub/spaces-sdks-docker
2
- # Vous y trouverez également des guides pour écrire au mieux votre Dockerfile
3
-
4
- FROM python:3.11
5
-
6
- # Création d'un utilisateur non-root avec UID 1000
7
- RUN useradd -m -u 1000 user
8
-
9
- # Passer à l'utilisateur non-root
10
- USER user
11
-
12
- # Ajouter le dossier local des binaires à PATH
13
- ENV PATH="/home/user/.local/bin:$PATH"
14
-
15
- # Définir le répertoire de travail
16
- WORKDIR /app
17
-
18
- # Copier le fichier requirements.txt avec les droits adéquats
19
- COPY --chown=user ./requirements.txt requirements.txt
20
-
21
- # Installer les dépendances Python
22
- RUN pip install --no-cache-dir --upgrade -r requirements.txt
23
-
24
- # Copier l'ensemble du projet dans le conteneur
25
- COPY --chown=user . /app
26
-
27
- # (Optionnel) Définir la variable d'environnement FLASK_APP
28
- # ENV FLASK_APP=app.py
29
-
30
- # Exposer le port utilisé par Flask
31
- EXPOSE 7860
32
-
33
- # Commande de démarrage de l'application Flask
34
- CMD ["flask", "run", "--host=0.0.0.0", "--port=7860"]
 
1
+ # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # you will also find guides on how best to write your Dockerfile
3
+
4
+ FROM python:3.11
5
+
6
+ RUN useradd -m -u 1000 user
7
+ USER user
8
+ ENV PATH="/home/user/.local/bin:$PATH"
9
+
10
+ WORKDIR /app
11
+
12
+ COPY --chown=user ./requirements.txt requirements.txt
13
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
14
+
15
+ COPY --chown=user . /app
16
+
17
+ CMD ["flask", "run", "--host=0.0.0.0", "--port=7860"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,8 +1,353 @@
1
- from api.index import app, db
 
 
 
2
 
3
- # Create database tables if they don't exist
4
- with app.app_context():
5
- db.create_all()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  if __name__ == '__main__':
 
 
 
8
  app.run(debug=True)
 
1
+ from flask import Flask, jsonify, render_template, request, redirect, url_for
2
+ from flask_sqlalchemy import SQLAlchemy
3
+ import os
4
+ from werkzeug.utils import secure_filename
5
 
6
+ app = Flask(__name__)
7
+ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///coursels.db'
8
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
9
+ UPLOAD_FOLDER = 'static/uploads'
10
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
11
+ if not os.path.exists(UPLOAD_FOLDER):
12
+ os.makedirs(UPLOAD_FOLDER)
13
+ db = SQLAlchemy(app)
14
+
15
+ class Subject(db.Model):
16
+ id = db.Column(db.Integer, primary_key=True)
17
+ name = db.Column(db.String(100), nullable=False)
18
+ icon_data = db.Column(db.LargeBinary, nullable=True) # Image data stored in DB
19
+ icon_filename = db.Column(db.String(200), nullable=True) # Original filename
20
+ icon_mimetype = db.Column(db.String(100), nullable=True) # MIME type
21
+ categories = db.relationship('Category', backref='subject', lazy=True)
22
+
23
+ class Category(db.Model):
24
+ id = db.Column(db.Integer, primary_key=True)
25
+ name = db.Column(db.String(100), nullable=False)
26
+ subject_id = db.Column(db.Integer, db.ForeignKey('subject.id'), nullable=False)
27
+ articles = db.relationship('Article', backref='category', lazy=True)
28
+
29
+ class Article(db.Model):
30
+ id = db.Column(db.Integer, primary_key=True)
31
+ title = db.Column(db.String(200), nullable=False)
32
+ content = db.Column(db.Text, nullable=False)
33
+ category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False)
34
+ icon_data = db.Column(db.LargeBinary, nullable=True) # Image data stored in DB
35
+ icon_filename = db.Column(db.String(200), nullable=True) # Original filename
36
+ icon_mimetype = db.Column(db.String(100), nullable=True) # MIME type
37
+
38
+ class Attachment(db.Model):
39
+ id = db.Column(db.Integer, primary_key=True)
40
+ data = db.Column(db.LargeBinary, nullable=False)
41
+ filename = db.Column(db.String(200), nullable=False)
42
+ mimetype = db.Column(db.String(100), nullable=False)
43
+
44
+ @app.route('/')
45
+ def home():
46
+ subjects = Subject.query.all()
47
+ # Add icon_url to each subject for template use
48
+ for subject in subjects:
49
+ if subject.icon_data:
50
+ subject.icon_url = url_for('get_subject_icon', subject_id=subject.id)
51
+ else:
52
+ subject.icon_url = None
53
+ return render_template('home.html', subjects=subjects)
54
+
55
+ @app.route('/subjects/<int:subject_id>/categories')
56
+ def categories(subject_id):
57
+ subject = Subject.query.get_or_404(subject_id)
58
+ categories = Category.query.filter_by(subject_id=subject_id).all()
59
+ return render_template('categories.html', subject=subject, categories=categories)
60
+
61
+ @app.route('/categories/<int:category_id>/articles')
62
+ def articles(category_id):
63
+ category = Category.query.get_or_404(category_id)
64
+ articles = Article.query.filter_by(category_id=category_id).all()
65
+ return render_template('articles.html', category=category, articles=articles)
66
+
67
+ @app.route('/articles/<int:article_id>')
68
+ def article(article_id):
69
+ article = Article.query.get_or_404(article_id)
70
+ return render_template('article.html', article=article)
71
+
72
+ # API endpoints for JSON if needed
73
+ @app.route('/api/subjects')
74
+ def api_subjects():
75
+ subjects = Subject.query.all()
76
+ subject_list = []
77
+ for s in subjects:
78
+ icon_url = url_for('get_subject_icon', subject_id=s.id) if s.icon_data else None
79
+ subject_list.append({'id': s.id, 'name': s.name, 'icon': icon_url})
80
+ return jsonify(subject_list)
81
+
82
+ @app.route('/api/subjects/<int:subject_id>/categories')
83
+ def api_categories(subject_id):
84
+ categories = Category.query.filter_by(subject_id=subject_id).all()
85
+ return jsonify([{'id': c.id, 'name': c.name} for c in categories])
86
+
87
+ @app.route('/api/categories/<int:category_id>/articles')
88
+ def api_articles(category_id):
89
+ articles = Article.query.filter_by(category_id=category_id).all()
90
+ return jsonify([{'id': a.id, 'title': a.title} for a in articles])
91
+
92
+ @app.route('/api/articles/<int:article_id>')
93
+ def api_article(article_id):
94
+ article = Article.query.get_or_404(article_id)
95
+ return jsonify({'id': article.id, 'title': article.title, 'content': article.content})
96
+
97
+ # Admin routes
98
+ @app.route('/admin')
99
+ def admin_home():
100
+ return render_template('admin_home.html')
101
+
102
+ @app.route('/admin/articles')
103
+ def admin_articles():
104
+ articles = Article.query.all()
105
+ return render_template('admin_articles.html', articles=articles)
106
+
107
+ @app.route('/admin/articles/edit/<int:article_id>', methods=['GET', 'POST'])
108
+ def admin_edit_article(article_id):
109
+ article = Article.query.get_or_404(article_id)
110
+ if request.method == 'POST':
111
+ article.title = request.form['title']
112
+ article.content = request.form['content']
113
+
114
+ # Handle file upload if provided
115
+ if 'icon' in request.files and request.files['icon'].filename:
116
+ file = request.files['icon']
117
+ file_data = file.read()
118
+ filename = secure_filename(file.filename)
119
+ article.icon_data = file_data
120
+ article.icon_filename = filename
121
+ article.icon_mimetype = file.mimetype
122
+
123
+ db.session.commit()
124
+ return redirect(url_for('admin_articles'))
125
+ return render_template('admin_edit_article.html', article=article)
126
+
127
+ @app.route('/admin/articles/new', methods=['GET', 'POST'])
128
+ def admin_new_article():
129
+ if request.method == 'POST':
130
+ title = request.form['title']
131
+ content = request.form['content']
132
+ category_id = request.form['category_id']
133
+ new_article = Article(title=title, content=content, category_id=category_id)
134
+ db.session.add(new_article)
135
+ db.session.commit()
136
+
137
+ # Handle file upload if provided
138
+ if 'icon' in request.files and request.files['icon'].filename:
139
+ file = request.files['icon']
140
+ file_data = file.read()
141
+ filename = secure_filename(file.filename)
142
+ new_article.icon_data = file_data
143
+ new_article.icon_filename = filename
144
+ new_article.icon_mimetype = file.mimetype
145
+ db.session.commit()
146
+
147
+ return redirect(url_for('admin_articles'))
148
+ categories = Category.query.all()
149
+ return render_template('admin_new_article.html', categories=categories)
150
+
151
+ @app.route('/admin/articles/delete/<int:article_id>', methods=['POST'])
152
+ def admin_delete_article(article_id):
153
+ article = Article.query.get_or_404(article_id)
154
+ db.session.delete(article)
155
+ db.session.commit()
156
+ return redirect(url_for('admin_articles'))
157
+
158
+ @app.route('/admin/subjects')
159
+ def admin_subjects():
160
+ subjects = Subject.query.all()
161
+ return render_template('admin_subjects.html', subjects=subjects)
162
+
163
+ @app.route('/admin/subjects/new', methods=['GET', 'POST'])
164
+ def admin_new_subject():
165
+ if request.method == 'POST':
166
+ name = request.form['name']
167
+ new_subject = Subject(name=name)
168
+ db.session.add(new_subject)
169
+ db.session.commit()
170
+
171
+ # Handle file upload if provided
172
+ if 'icon' in request.files and request.files['icon'].filename:
173
+ file = request.files['icon']
174
+ file_data = file.read()
175
+ filename = secure_filename(file.filename)
176
+ new_subject.icon_data = file_data
177
+ new_subject.icon_filename = filename
178
+ new_subject.icon_mimetype = file.mimetype
179
+ db.session.commit()
180
+
181
+ return redirect(url_for('admin_subjects'))
182
+ return render_template('admin_new_subject.html')
183
+
184
+ @app.route('/admin/subjects/edit/<int:subject_id>', methods=['GET', 'POST'])
185
+ def admin_edit_subject(subject_id):
186
+ subject = Subject.query.get_or_404(subject_id)
187
+ if request.method == 'POST':
188
+ subject.name = request.form['name']
189
+
190
+ # Handle file upload if provided
191
+ if 'icon' in request.files and request.files['icon'].filename:
192
+ file = request.files['icon']
193
+ file_data = file.read()
194
+ filename = secure_filename(file.filename)
195
+ subject.icon_data = file_data
196
+ subject.icon_filename = filename
197
+ subject.icon_mimetype = file.mimetype
198
+
199
+ db.session.commit()
200
+ return redirect(url_for('admin_subjects'))
201
+ return render_template('admin_edit_subject.html', subject=subject)
202
+
203
+ @app.route('/admin/subjects/delete/<int:subject_id>', methods=['POST'])
204
+ def admin_delete_subject(subject_id):
205
+ subject = Subject.query.get_or_404(subject_id)
206
+ db.session.delete(subject)
207
+ db.session.commit()
208
+ return redirect(url_for('admin_subjects'))
209
+
210
+ @app.route('/admin/categories')
211
+ def admin_categories():
212
+ categories = Category.query.all()
213
+ return render_template('admin_categories.html', categories=categories)
214
+
215
+ @app.route('/admin/categories/new', methods=['GET', 'POST'])
216
+ def admin_new_category():
217
+ if request.method == 'POST':
218
+ name = request.form['name']
219
+ subject_id = request.form['subject_id']
220
+ new_category = Category(name=name, subject_id=subject_id)
221
+ db.session.add(new_category)
222
+ db.session.commit()
223
+ return redirect(url_for('admin_categories'))
224
+ subjects = Subject.query.all()
225
+ return render_template('admin_new_category.html', subjects=subjects)
226
+
227
+ @app.route('/admin/categories/edit/<int:category_id>', methods=['GET', 'POST'])
228
+ def admin_edit_category(category_id):
229
+ category = Category.query.get_or_404(category_id)
230
+ if request.method == 'POST':
231
+ category.name = request.form['name']
232
+ category.subject_id = request.form['subject_id']
233
+ db.session.commit()
234
+ return redirect(url_for('admin_categories'))
235
+ subjects = Subject.query.all()
236
+ return render_template('admin_edit_category.html', category=category, subjects=subjects)
237
+
238
+ @app.route('/admin/categories/delete/<int:category_id>', methods=['POST'])
239
+ def admin_delete_category(category_id):
240
+ category = Category.query.get_or_404(category_id)
241
+ db.session.delete(category)
242
+ db.session.commit()
243
+ return redirect(url_for('admin_categories'))
244
+
245
+ def add_sample_data():
246
+ if Subject.query.count() == 0:
247
+ math = Subject(name="Mathématiques")
248
+ physics = Subject(name="Physique")
249
+ db.session.add(math)
250
+ db.session.add(physics)
251
+ db.session.commit()
252
+
253
+ algebra = Category(name="Algèbre", subject=math)
254
+ geometry = Category(name="Géométrie", subject=math)
255
+ mechanics = Category(name="Mécanique", subject=physics)
256
+ db.session.add(algebra)
257
+ db.session.add(geometry)
258
+ db.session.add(mechanics)
259
+ db.session.commit()
260
+
261
+ Article(title="Équations linéaires", content="<p>Les équations linéaires sont de la forme ax + b = 0.</p>", category=algebra)
262
+ Article(title="Triangles", content="<p>Un triangle a trois côtés.</p>", category=geometry)
263
+ Article(title="Lois de Newton", content="<p>La première loi de Newton...</p>", category=mechanics)
264
+ db.session.commit()
265
+
266
+ @app.route('/upload/subject/<int:subject_id>', methods=['POST'])
267
+ def upload_subject_icon(subject_id):
268
+ subject = Subject.query.get_or_404(subject_id)
269
+ if 'file' not in request.files:
270
+ return jsonify({'error': 'No file'}), 400
271
+ file = request.files['file']
272
+ if file.filename == '':
273
+ return jsonify({'error': 'No selected file'}), 400
274
+
275
+ # Read file data
276
+ file_data = file.read()
277
+ filename = secure_filename(file.filename)
278
+
279
+ # Store in database
280
+ subject.icon_data = file_data
281
+ subject.icon_filename = filename
282
+ subject.icon_mimetype = file.mimetype
283
+ db.session.commit()
284
+
285
+ return jsonify({'success': True, 'filename': filename})
286
+
287
+ @app.route('/upload/article/<int:article_id>', methods=['POST'])
288
+ def upload_article_image(article_id):
289
+ article = Article.query.get_or_404(article_id)
290
+ if 'file' not in request.files:
291
+ return jsonify({'error': 'No file'}), 400
292
+ file = request.files['file']
293
+ if file.filename == '':
294
+ return jsonify({'error': 'No selected file'}), 400
295
+
296
+ # Read file data
297
+ file_data = file.read()
298
+ filename = secure_filename(file.filename)
299
+
300
+ # Store in database
301
+ article.icon_data = file_data
302
+ article.icon_filename = filename
303
+ article.icon_mimetype = file.mimetype
304
+ db.session.commit()
305
+
306
+ return jsonify({'success': True, 'filename': filename})
307
+
308
+ @app.route('/upload', methods=['POST'])
309
+ def upload_attachment():
310
+ if 'file' not in request.files:
311
+ return jsonify({'error': 'No file'}), 400
312
+ file = request.files['file']
313
+ if file.filename == '':
314
+ return jsonify({'error': 'No selected file'}), 400
315
+
316
+ # Read file data
317
+ file_data = file.read()
318
+ filename = secure_filename(file.filename)
319
+
320
+ # Store in database
321
+ attachment = Attachment(data=file_data, filename=filename, mimetype=file.mimetype)
322
+ db.session.add(attachment)
323
+ db.session.commit()
324
+
325
+ url = url_for('get_attachment', attachment_id=attachment.id)
326
+ return jsonify({'url': url})
327
+
328
+ @app.route('/subject/<int:subject_id>/icon')
329
+ def get_subject_icon(subject_id):
330
+ subject = Subject.query.get_or_404(subject_id)
331
+ if subject.icon_data:
332
+ return subject.icon_data, 200, {'Content-Type': subject.icon_mimetype or 'image/png'}
333
+ else:
334
+ return '', 404
335
+
336
+ @app.route('/article/<int:article_id>/image')
337
+ def get_article_image(article_id):
338
+ article = Article.query.get_or_404(article_id)
339
+ if article.icon_data:
340
+ return article.icon_data, 200, {'Content-Type': article.icon_mimetype or 'image/png'}
341
+ else:
342
+ return '', 404
343
+
344
+ @app.route('/attachment/<int:attachment_id>')
345
+ def get_attachment(attachment_id):
346
+ attachment = Attachment.query.get_or_404(attachment_id)
347
+ return attachment.data, 200, {'Content-Type': attachment.mimetype}
348
 
349
  if __name__ == '__main__':
350
+ with app.app_context():
351
+ db.create_all()
352
+ add_sample_data()
353
  app.run(debug=True)
main.py CHANGED
@@ -1,10 +1,10 @@
1
- import os
2
- from api.index import app, db
3
-
4
- # Create database tables if they don't exist
5
- with app.app_context():
6
- db.create_all()
7
-
8
- if __name__ == '__main__':
9
- port = int(os.environ.get("PORT", 5000))
10
  app.run(host="0.0.0.0", port=port)
 
1
+ import os
2
+ from api.index import app, db
3
+
4
+ # Create database tables if they don't exist
5
+ with app.app_context():
6
+ db.create_all()
7
+
8
+ if __name__ == '__main__':
9
+ port = int(os.environ.get("PORT", 5000))
10
  app.run(host="0.0.0.0", port=port)