Taf2023 commited on
Commit
2a8ec68
·
verified ·
1 Parent(s): f93593b

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -910
app.py DELETED
@@ -1,910 +0,0 @@
1
- import os
2
- from datetime import datetime
3
- from flask import Flask, request, jsonify, render_template_string
4
- from flask_sqlalchemy import SQLAlchemy
5
- from flask_cors import CORS
6
-
7
- app = Flask(__name__)
8
- app.config['SECRET_KEY'] = 'asdf#FGSgvasgf$5$WGT'
9
-
10
- # Enable CORS for all routes
11
- CORS(app)
12
-
13
- # Database configuration
14
- app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
15
- app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
16
- db = SQLAlchemy(app)
17
-
18
- # Database Models
19
- class Post(db.Model):
20
- id = db.Column(db.Integer, primary_key=True)
21
- name = db.Column(db.String(100), nullable=False)
22
- note = db.Column(db.Text, nullable=False)
23
- youtube_link = db.Column(db.String(500), nullable=True)
24
- likes = db.Column(db.Integer, default=0)
25
- created_at = db.Column(db.DateTime, default=datetime.utcnow)
26
-
27
- def to_dict(self):
28
- return {
29
- 'id': self.id,
30
- 'name': self.name,
31
- 'note': self.note,
32
- 'youtube_link': self.youtube_link,
33
- 'likes': self.likes,
34
- 'created_at': self.created_at.isoformat() if self.created_at else None
35
- }
36
-
37
- class Comment(db.Model):
38
- id = db.Column(db.Integer, primary_key=True)
39
- post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
40
- name = db.Column(db.String(100), nullable=False)
41
- comment = db.Column(db.Text, nullable=False)
42
- created_at = db.Column(db.DateTime, default=datetime.utcnow)
43
-
44
- def to_dict(self):
45
- return {
46
- 'id': self.id,
47
- 'post_id': self.post_id,
48
- 'name': self.name,
49
- 'comment': self.comment,
50
- 'created_at': self.created_at.isoformat() if self.created_at else None
51
- }
52
-
53
- # Create database tables
54
- with app.app_context():
55
- db.create_all()
56
-
57
- # HTML Template
58
- HTML_TEMPLATE = '''
59
- <!DOCTYPE html>
60
- <html lang="th">
61
- <head>
62
- <meta charset="UTF-8">
63
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
64
- <title>Share YouTube - แชร์วิดีโอและโน๊ตกับเพื่อน</title>
65
- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
66
- <style>
67
- * {
68
- margin: 0;
69
- padding: 0;
70
- box-sizing: border-box;
71
- }
72
-
73
- body {
74
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
75
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
76
- min-height: 100vh;
77
- color: #333;
78
- }
79
-
80
- .container {
81
- max-width: 800px;
82
- margin: 0 auto;
83
- padding: 20px;
84
- }
85
-
86
- .header {
87
- text-align: center;
88
- margin-bottom: 30px;
89
- background: rgba(255, 255, 255, 0.95);
90
- padding: 30px;
91
- border-radius: 15px;
92
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
93
- backdrop-filter: blur(10px);
94
- }
95
-
96
- .header h1 {
97
- font-size: 2.5rem;
98
- color: #FF0000;
99
- margin-bottom: 10px;
100
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
101
- }
102
-
103
- .header p {
104
- font-size: 1.1rem;
105
- color: #666;
106
- }
107
-
108
- .share-section {
109
- margin-bottom: 30px;
110
- }
111
-
112
- .share-card {
113
- background: rgba(255, 255, 255, 0.95);
114
- padding: 30px;
115
- border-radius: 15px;
116
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
117
- backdrop-filter: blur(10px);
118
- }
119
-
120
- .share-card h2 {
121
- color: #333;
122
- margin-bottom: 25px;
123
- font-size: 1.5rem;
124
- text-align: center;
125
- }
126
-
127
- .form-group {
128
- margin-bottom: 20px;
129
- }
130
-
131
- .form-group label {
132
- display: block;
133
- margin-bottom: 8px;
134
- font-weight: 600;
135
- color: #555;
136
- }
137
-
138
- .form-group input,
139
- .form-group textarea {
140
- width: 100%;
141
- padding: 12px 15px;
142
- border: 2px solid #e1e5e9;
143
- border-radius: 10px;
144
- font-size: 1rem;
145
- transition: all 0.3s ease;
146
- background: rgba(255, 255, 255, 0.9);
147
- }
148
-
149
- .form-group input:focus,
150
- .form-group textarea:focus {
151
- outline: none;
152
- border-color: #FF0000;
153
- box-shadow: 0 0 0 3px rgba(255, 0, 0, 0.1);
154
- transform: translateY(-2px);
155
- }
156
-
157
- .form-group textarea {
158
- resize: vertical;
159
- min-height: 80px;
160
- }
161
-
162
- .btn-share {
163
- width: 100%;
164
- padding: 15px;
165
- background: linear-gradient(45deg, #FF0000, #CC0000);
166
- color: white;
167
- border: none;
168
- border-radius: 10px;
169
- font-size: 1.1rem;
170
- font-weight: 600;
171
- cursor: pointer;
172
- transition: all 0.3s ease;
173
- text-transform: uppercase;
174
- letter-spacing: 1px;
175
- }
176
-
177
- .btn-share:hover {
178
- transform: translateY(-2px);
179
- box-shadow: 0 8px 25px rgba(255, 0, 0, 0.3);
180
- }
181
-
182
- .feed-section {
183
- background: rgba(255, 255, 255, 0.95);
184
- padding: 30px;
185
- border-radius: 15px;
186
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
187
- backdrop-filter: blur(10px);
188
- }
189
-
190
- .feed-section h2 {
191
- color: #333;
192
- margin-bottom: 25px;
193
- font-size: 1.5rem;
194
- text-align: center;
195
- }
196
-
197
- .link-card {
198
- background: #fff;
199
- border: 1px solid #e1e5e9;
200
- border-radius: 12px;
201
- padding: 20px;
202
- margin-bottom: 20px;
203
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
204
- transition: all 0.3s ease;
205
- }
206
-
207
- .link-card:hover {
208
- transform: translateY(-3px);
209
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
210
- }
211
-
212
- .link-header {
213
- display: flex;
214
- align-items: center;
215
- margin-bottom: 15px;
216
- }
217
-
218
- .link-header .user-name {
219
- font-weight: 600;
220
- color: #333;
221
- margin-right: 10px;
222
- }
223
-
224
- .link-header .timestamp {
225
- color: #888;
226
- font-size: 0.9rem;
227
- }
228
-
229
- .link-note {
230
- margin-bottom: 15px;
231
- color: #555;
232
- line-height: 1.5;
233
- }
234
-
235
- .youtube-embed {
236
- margin-bottom: 15px;
237
- border-radius: 8px;
238
- overflow: hidden;
239
- }
240
-
241
- .youtube-embed iframe {
242
- width: 100%;
243
- height: 315px;
244
- border: none;
245
- }
246
-
247
- .link-actions {
248
- display: flex;
249
- gap: 15px;
250
- padding-top: 15px;
251
- border-top: 1px solid #e1e5e9;
252
- }
253
-
254
- .action-btn {
255
- background: none;
256
- border: none;
257
- padding: 8px 15px;
258
- border-radius: 20px;
259
- cursor: pointer;
260
- transition: all 0.3s ease;
261
- font-size: 0.9rem;
262
- display: flex;
263
- align-items: center;
264
- gap: 5px;
265
- }
266
-
267
- .like-btn {
268
- color: #666;
269
- }
270
-
271
- .like-btn:hover,
272
- .like-btn.liked {
273
- background: rgba(255, 0, 0, 0.1);
274
- color: #FF0000;
275
- }
276
-
277
- .comment-btn {
278
- color: #666;
279
- }
280
-
281
- .comment-btn:hover {
282
- background: rgba(0, 123, 255, 0.1);
283
- color: #007bff;
284
- }
285
-
286
- .comments-section {
287
- margin-top: 15px;
288
- padding-top: 15px;
289
- border-top: 1px solid #e1e5e9;
290
- }
291
-
292
- .comment-form {
293
- display: flex;
294
- gap: 10px;
295
- margin-bottom: 15px;
296
- }
297
-
298
- .comment-form input {
299
- flex: 1;
300
- padding: 8px 12px;
301
- border: 1px solid #ddd;
302
- border-radius: 20px;
303
- font-size: 0.9rem;
304
- }
305
-
306
- .comment-form button {
307
- padding: 8px 15px;
308
- background: #007bff;
309
- color: white;
310
- border: none;
311
- border-radius: 20px;
312
- cursor: pointer;
313
- font-size: 0.9rem;
314
- }
315
-
316
- .comment-form button:hover {
317
- background: #0056b3;
318
- }
319
-
320
- .comment {
321
- background: #f8f9fa;
322
- padding: 10px 15px;
323
- border-radius: 10px;
324
- margin-bottom: 10px;
325
- }
326
-
327
- .comment-author {
328
- font-weight: 600;
329
- color: #333;
330
- margin-bottom: 5px;
331
- }
332
-
333
- .comment-text {
334
- color: #555;
335
- font-size: 0.9rem;
336
- }
337
-
338
- .loading-spinner {
339
- position: fixed;
340
- top: 50%;
341
- left: 50%;
342
- transform: translate(-50%, -50%);
343
- background: rgba(255, 255, 255, 0.95);
344
- padding: 30px;
345
- border-radius: 15px;
346
- text-align: center;
347
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
348
- display: none;
349
- }
350
-
351
- .loading-spinner i {
352
- font-size: 2rem;
353
- color: #FF0000;
354
- margin-bottom: 10px;
355
- }
356
-
357
- .success-message {
358
- position: fixed;
359
- top: 20px;
360
- right: 20px;
361
- background: #28a745;
362
- color: white;
363
- padding: 15px 20px;
364
- border-radius: 10px;
365
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
366
- display: none;
367
- align-items: center;
368
- gap: 10px;
369
- z-index: 1000;
370
- }
371
-
372
- .success-message i {
373
- font-size: 1.2rem;
374
- }
375
-
376
- @media (max-width: 768px) {
377
- .container {
378
- padding: 15px;
379
- }
380
-
381
- .header h1 {
382
- font-size: 2rem;
383
- }
384
-
385
- .share-card,
386
- .feed-section {
387
- padding: 20px;
388
- }
389
-
390
- .youtube-embed iframe {
391
- height: 200px;
392
- }
393
-
394
- .link-actions {
395
- flex-wrap: wrap;
396
- }
397
- }
398
-
399
- @keyframes fadeInUp {
400
- from {
401
- opacity: 0;
402
- transform: translateY(30px);
403
- }
404
- to {
405
- opacity: 1;
406
- transform: translateY(0);
407
- }
408
- }
409
-
410
- .link-card {
411
- animation: fadeInUp 0.5s ease;
412
- }
413
- </style>
414
- </head>
415
- <body>
416
- <div class="container">
417
- <!-- Header -->
418
- <header class="header">
419
- <div class="header-content">
420
- <h1><i class="fab fa-youtube"></i> Share YouTube</h1>
421
- <p>แชร์วิดีโอและโน๊ตกับเพื่อน</p>
422
- </div>
423
- </header>
424
-
425
- <!-- Share Form -->
426
- <div class="share-section">
427
- <div class="share-card">
428
- <h2><i class="fas fa-share"></i> แชร์ลิงก์ YouTube</h2>
429
- <form id="shareForm">
430
- <div class="form-group">
431
- <label for="name"><i class="fas fa-user"></i> ชื่อของคุณ:</label>
432
- <input type="text" id="name" name="name" required placeholder="กรอกชื่อของคุณ">
433
- </div>
434
-
435
- <div class="form-group">
436
- <label for="youtube_link"><i class="fab fa-youtube"></i> ลิงก์ YouTube:</label>
437
- <input type="url" id="youtube_link" name="youtube_link" required placeholder="https://www.youtube.com/watch?v=...">
438
- </div>
439
-
440
- <div class="form-group">
441
- <label for="note"><i class="fas fa-sticky-note"></i> โน๊ตสั้นๆ:</label>
442
- <textarea id="note" name="note" required placeholder="เขียนโน๊ตสั้นๆ เกี่ยวกับวิดีโอนี้..."></textarea>
443
- </div>
444
-
445
- <button type="submit" class="btn-share">
446
- <i class="fas fa-share"></i> แชร์
447
- </button>
448
- </form>
449
- </div>
450
- </div>
451
-
452
- <!-- Shared Links Feed -->
453
- <div class="feed-section">
454
- <h2><i class="fas fa-list"></i> ลิงก์ที่แชร์</h2>
455
- <div id="linksContainer">
456
- <!-- Shared links will be loaded here -->
457
- </div>
458
- </div>
459
- </div>
460
-
461
- <!-- Loading Spinner -->
462
- <div id="loadingSpinner" class="loading-spinner">
463
- <i class="fas fa-spinner fa-spin"></i>
464
- <p>กำลังโหลด...</p>
465
- </div>
466
-
467
- <!-- Success Message -->
468
- <div id="successMessage" class="success-message">
469
- <i class="fas fa-check-circle"></i>
470
- <span id="successText">แชร์สำเร็จ!</span>
471
- </div>
472
-
473
- <script>
474
- // API Base URL
475
- const API_BASE_URL = '/api';
476
-
477
- // DOM Elements
478
- const shareForm = document.getElementById('shareForm');
479
- const linksContainer = document.getElementById('linksContainer');
480
- const loadingSpinner = document.getElementById('loadingSpinner');
481
- const successMessage = document.getElementById('successMessage');
482
- const successText = document.getElementById('successText');
483
-
484
- // Initialize app
485
- document.addEventListener('DOMContentLoaded', function() {
486
- loadLinks();
487
-
488
- // Handle form submission
489
- shareForm.addEventListener('submit', handleShareSubmit);
490
- });
491
-
492
- // Handle share form submission
493
- async function handleShareSubmit(e) {
494
- e.preventDefault();
495
-
496
- const formData = new FormData(shareForm);
497
- const data = {
498
- name: formData.get('name'),
499
- note: formData.get('note'),
500
- youtube_link: formData.get('youtube_link')
501
- };
502
-
503
- // Validate YouTube URL
504
- if (!isValidYouTubeURL(data.youtube_link)) {
505
- alert('กรุณากรอกลิงก์ YouTube ที่ถูกต้อง');
506
- return;
507
- }
508
-
509
- try {
510
- showLoading(true);
511
-
512
- const response = await fetch(`${API_BASE_URL}/posts`, {
513
- method: 'POST',
514
- headers: {
515
- 'Content-Type': 'application/json',
516
- },
517
- body: JSON.stringify(data)
518
- });
519
-
520
- if (!response.ok) {
521
- throw new Error('เกิดข้อผิดพลาดในการแชร์');
522
- }
523
-
524
- const result = await response.json();
525
-
526
- // Show success message
527
- showSuccessMessage('แชร์สำเร็จ!');
528
-
529
- // Clear form
530
- shareForm.reset();
531
-
532
- // Reload links
533
- await loadLinks();
534
-
535
- } catch (error) {
536
- console.error('Error sharing link:', error);
537
- alert('เกิดข้อผิดพลาดในการแชร์: ' + error.message);
538
- } finally {
539
- showLoading(false);
540
- }
541
- }
542
-
543
- // Load all shared links
544
- async function loadLinks() {
545
- try {
546
- showLoading(true);
547
-
548
- const response = await fetch(`${API_BASE_URL}/posts`);
549
- if (!response.ok) {
550
- throw new Error('ไม่สามารถโหลดลิงก์ได้');
551
- }
552
-
553
- const links = await response.json();
554
- displayLinks(links);
555
-
556
- } catch (error) {
557
- console.error('Error loading links:', error);
558
- linksContainer.innerHTML = `
559
- <div style="text-align: center; color: #666; padding: 20px;">
560
- <i class="fas fa-exclamation-triangle"></i>
561
- <p>ไม่สามารถโหลดลิงก์ได้: ${error.message}</p>
562
- </div>
563
- `;
564
- } finally {
565
- showLoading(false);
566
- }
567
- }
568
-
569
- // Display links in the container
570
- function displayLinks(links) {
571
- if (links.length === 0) {
572
- linksContainer.innerHTML = `
573
- <div style="text-align: center; color: #666; padding: 40px;">
574
- <i class="fab fa-youtube" style="font-size: 3rem; margin-bottom: 15px;"></i>
575
- <p>ยังไม่มีลิงก์ที่แชร์</p>
576
- <p>เป็นคนแรกที่แชร์ลิงก์ YouTube!</p>
577
- </div>
578
- `;
579
- return;
580
- }
581
-
582
- linksContainer.innerHTML = links.map(link => createLinkCard(link)).join('');
583
-
584
- // Add event listeners for like and comment buttons
585
- addLinkEventListeners();
586
- }
587
-
588
- // Create HTML for a single link card
589
- function createLinkCard(link) {
590
- const videoId = extractYouTubeVideoId(link.youtube_link);
591
- const embedUrl = videoId ? `https://www.youtube.com/embed/${videoId}` : '';
592
- const timeAgo = getTimeAgo(new Date(link.created_at));
593
-
594
- return `
595
- <div class="link-card" data-link-id="${link.id}">
596
- <div class="link-header">
597
- <span class="user-name">
598
- <i class="fas fa-user"></i> ${escapeHtml(link.name)}
599
- </span>
600
- <span class="timestamp">${timeAgo}</span>
601
- </div>
602
-
603
- <div class="link-note">
604
- ${escapeHtml(link.note)}
605
- </div>
606
-
607
- ${embedUrl ? `
608
- <div class="youtube-embed">
609
- <iframe src="${embedUrl}"
610
- title="YouTube video player"
611
- frameborder="0"
612
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
613
- allowfullscreen>
614
- </iframe>
615
- </div>
616
- ` : `
617
- <div class="youtube-link">
618
- <a href="${link.youtube_link}" target="_blank" rel="noopener noreferrer">
619
- <i class="fab fa-youtube"></i> ดูวิดีโอใน YouTube
620
- </a>
621
- </div>
622
- `}
623
-
624
- <div class="link-actions">
625
- <button class="action-btn like-btn" data-link-id="${link.id}">
626
- <i class="fas fa-heart"></i>
627
- <span class="like-count">${link.likes}</span>
628
- </button>
629
- <button class="action-btn comment-btn" data-link-id="${link.id}">
630
- <i class="fas fa-comment"></i>
631
- คอมเมนต์
632
- </button>
633
- </div>
634
-
635
- <div class="comments-section" id="comments-${link.id}" style="display: none;">
636
- <div class="comment-form">
637
- <input type="text" placeholder="เขียนคอมเมนต์..." class="comment-input">
638
- <button type="button" class="add-comment-btn" data-link-id="${link.id}">
639
- <i class="fas fa-paper-plane"></i>
640
- </button>
641
- </div>
642
- <div class="comments-list" id="comments-list-${link.id}">
643
- <!-- Comments will be loaded here -->
644
- </div>
645
- </div>
646
- </div>
647
- `;
648
- }
649
-
650
- // Add event listeners for link interactions
651
- function addLinkEventListeners() {
652
- // Like buttons
653
- document.querySelectorAll('.like-btn').forEach(btn => {
654
- btn.addEventListener('click', handleLike);
655
- });
656
-
657
- // Comment buttons
658
- document.querySelectorAll('.comment-btn').forEach(btn => {
659
- btn.addEventListener('click', toggleComments);
660
- });
661
-
662
- // Add comment buttons
663
- document.querySelectorAll('.add-comment-btn').forEach(btn => {
664
- btn.addEventListener('click', handleAddComment);
665
- });
666
-
667
- // Enter key for comment input
668
- document.querySelectorAll('.comment-input').forEach(input => {
669
- input.addEventListener('keypress', function(e) {
670
- if (e.key === 'Enter') {
671
- const linkId = this.closest('.comments-section').id.split('-')[1];
672
- const btn = document.querySelector(`.add-comment-btn[data-link-id="${linkId}"]`);
673
- btn.click();
674
- }
675
- });
676
- });
677
- }
678
-
679
- // Handle like button click
680
- async function handleLike(e) {
681
- const linkId = e.currentTarget.dataset.linkId;
682
- const likeBtn = e.currentTarget;
683
- const likeCount = likeBtn.querySelector('.like-count');
684
-
685
- try {
686
- const response = await fetch(`${API_BASE_URL}/posts/${linkId}/like`, {
687
- method: 'POST'
688
- });
689
-
690
- if (!response.ok) {
691
- throw new Error('ไม่สามารถถูกใจได้');
692
- }
693
-
694
- const result = await response.json();
695
- likeCount.textContent = result.likes;
696
-
697
- // Add visual feedback
698
- likeBtn.classList.add('liked');
699
- setTimeout(() => likeBtn.classList.remove('liked'), 1000);
700
-
701
- } catch (error) {
702
- console.error('Error liking post:', error);
703
- alert('เกิดข้อผิดพลาดในการถูกใจ');
704
- }
705
- }
706
-
707
- // Toggle comments section
708
- async function toggleComments(e) {
709
- const linkId = e.currentTarget.dataset.linkId;
710
- const commentsSection = document.getElementById(`comments-${linkId}`);
711
-
712
- if (commentsSection.style.display === 'none') {
713
- commentsSection.style.display = 'block';
714
- await loadComments(linkId);
715
- } else {
716
- commentsSection.style.display = 'none';
717
- }
718
- }
719
-
720
- // Load comments for a specific link
721
- async function loadComments(linkId) {
722
- try {
723
- const response = await fetch(`${API_BASE_URL}/posts/${linkId}/comments`);
724
- if (!response.ok) {
725
- throw new Error('ไม่สามารถโหลดคอมเมนต์ได้');
726
- }
727
-
728
- const comments = await response.json();
729
- const commentsList = document.getElementById(`comments-list-${linkId}`);
730
-
731
- if (comments.length === 0) {
732
- commentsList.innerHTML = '<p style="text-align: center; color: #666; padding: 10px;">ยังไม่มีคอมเมนต์</p>';
733
- } else {
734
- commentsList.innerHTML = comments.map(comment => `
735
- <div class="comment">
736
- <div class="comment-author">
737
- <i class="fas fa-user"></i> ${escapeHtml(comment.name)}
738
- </div>
739
- <div class="comment-text">${escapeHtml(comment.comment)}</div>
740
- </div>
741
- `).join('');
742
- }
743
-
744
- } catch (error) {
745
- console.error('Error loading comments:', error);
746
- }
747
- }
748
-
749
- // Handle add comment
750
- async function handleAddComment(e) {
751
- const linkId = e.currentTarget.dataset.linkId;
752
- const commentsSection = document.getElementById(`comments-${linkId}`);
753
- const commentInput = commentsSection.querySelector('.comment-input');
754
- const commentText = commentInput.value.trim();
755
-
756
- if (!commentText) {
757
- alert('กรุณาเขียนคอมเมนต์');
758
- return;
759
- }
760
-
761
- // Simple name prompt (in real app, you'd have user authentication)
762
- const name = prompt('กรุณากรอกชื่อของคุณ:');
763
- if (!name) return;
764
-
765
- try {
766
- const response = await fetch(`${API_BASE_URL}/posts/${linkId}/comments`, {
767
- method: 'POST',
768
- headers: {
769
- 'Content-Type': 'application/json',
770
- },
771
- body: JSON.stringify({
772
- name: name,
773
- comment: commentText
774
- })
775
- });
776
-
777
- if (!response.ok) {
778
- throw new Error('ไม่สามารถเพิ่มคอมเมนต์ได้');
779
- }
780
-
781
- // Clear input
782
- commentInput.value = '';
783
-
784
- // Reload comments
785
- await loadComments(linkId);
786
-
787
- showSuccessMessage('เพิ่มคอมเมนต์สำเร็จ!');
788
-
789
- } catch (error) {
790
- console.error('Error adding comment:', error);
791
- alert('เกิดข้อผิดพลาดในการเพิ่มคอมเมนต์');
792
- }
793
- }
794
-
795
- // Utility functions
796
- function isValidYouTubeURL(url) {
797
- const youtubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)[a-zA-Z0-9_-]{11}/;
798
- return youtubeRegex.test(url);
799
- }
800
-
801
- function extractYouTubeVideoId(url) {
802
- const regex = /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/;
803
- const match = url.match(regex);
804
- return match ? match[1] : null;
805
- }
806
-
807
- function escapeHtml(text) {
808
- const div = document.createElement('div');
809
- div.textContent = text;
810
- return div.innerHTML;
811
- }
812
-
813
- function getTimeAgo(date) {
814
- const now = new Date();
815
- const diffInSeconds = Math.floor((now - date) / 1000);
816
-
817
- if (diffInSeconds < 60) return 'เมื่อสักครู่';
818
- if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} นาทีที่แล้ว`;
819
- if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} ชั่วโมงที่แล้ว`;
820
- return `${Math.floor(diffInSeconds / 86400)} วันที่แล้ว`;
821
- }
822
-
823
- function showLoading(show) {
824
- loadingSpinner.style.display = show ? 'block' : 'none';
825
- }
826
-
827
- function showSuccessMessage(message) {
828
- successText.textContent = message;
829
- successMessage.style.display = 'flex';
830
- setTimeout(() => {
831
- successMessage.style.display = 'none';
832
- }, 3000);
833
- }
834
- </script>
835
- </body>
836
- </html>
837
- '''
838
-
839
- # API Routes
840
- @app.route('/api/posts', methods=['GET'])
841
- def get_posts():
842
- """ดึงโพสต์ทั้งหมด"""
843
- posts = Post.query.order_by(Post.created_at.desc()).all()
844
- return jsonify([post.to_dict() for post in posts])
845
-
846
- @app.route('/api/posts', methods=['POST'])
847
- def create_post():
848
- """สร้างโพสต์ใหม่"""
849
- data = request.get_json()
850
-
851
- if not data or not data.get('name') or not data.get('note'):
852
- return jsonify({'error': 'Name and note are required'}), 400
853
-
854
- post = Post(
855
- name=data['name'],
856
- note=data['note'],
857
- youtube_link=data.get('youtube_link', '')
858
- )
859
-
860
- db.session.add(post)
861
- db.session.commit()
862
-
863
- return jsonify(post.to_dict()), 201
864
-
865
- @app.route('/api/posts/<int:post_id>/like', methods=['POST'])
866
- def like_post(post_id):
867
- """ถูกใจโพสต์"""
868
- post = Post.query.get_or_404(post_id)
869
- post.likes += 1
870
- db.session.commit()
871
-
872
- return jsonify({'likes': post.likes})
873
-
874
- @app.route('/api/posts/<int:post_id>/comments', methods=['GET'])
875
- def get_comments(post_id):
876
- """ดึงคอมเมนต์ของโพสต์"""
877
- comments = Comment.query.filter_by(post_id=post_id).order_by(Comment.created_at.asc()).all()
878
- return jsonify([comment.to_dict() for comment in comments])
879
-
880
- @app.route('/api/posts/<int:post_id>/comments', methods=['POST'])
881
- def add_comment(post_id):
882
- """เพิ่มคอมเมนต์ในโพสต์"""
883
- data = request.get_json()
884
-
885
- if not data or not data.get('name') or not data.get('comment'):
886
- return jsonify({'error': 'Name and comment are required'}), 400
887
-
888
- # ตรวจสอบว่าโพสต์มีอยู่จริง
889
- post = Post.query.get_or_404(post_id)
890
-
891
- comment = Comment(
892
- post_id=post_id,
893
- name=data['name'],
894
- comment=data['comment']
895
- )
896
-
897
- db.session.add(comment)
898
- db.session.commit()
899
-
900
- return jsonify(comment.to_dict()), 201
901
-
902
- # Main route
903
- @app.route('/')
904
- def index():
905
- return render_template_string(HTML_TEMPLATE)
906
-
907
- if __name__ == '__main__':
908
- port = int(os.environ.get('PORT', 7860))
909
- app.run(host='0.0.0.0', port=port, debug=False)
910
-