Supabase থেকে Firebase-এ মাইগ্রেশন: ব্যাকএন্ড ছাড়াই ফায়ারস্টোরে যোগাযোগ ফর্ম সংযুক্ত করুন
Publié le 29 April 2026
- দৃশ্য: একটি ফর্ম যা কোনো কিছু সংরক্ষণ করে না
- Firebase কেন Supabase এর পরিবর্তে ?
- ফেজ ১ : ফায়ারবেস প্রকল্প তৈরি করুন
- ফেজ ২: জমার জাভাস্ক্রিপ্ট পুনরলিখুন
- ফেজ ৩: Supabase-তে মৃত কোড পরিষ্কার করুন
- ফেজ 4 : ফুটার কনফিগার করুন
- ফেজ 5 : শেষ আর্কিটেকচার
- এয়া মাইগ্রেশন ডগফুডিং এর বRGে যা Bale
- আগামী ধাপ (backlog)
- উৎস
মাসों 동안, এই সাইটের যোগাযোগ ফর্মটি একটি mock JavaScript-এ চলে আসি — ৮৫% সাফল্যের প্রতিশ্রুতি, একটি মজ Firestore, শून্য সংগৃহীত ডেটা। প্রাথমিক পরিকল্পনা Supabase ব্যাকএন্ড এবং Google Apps Script-এর মাধ্যমে মেইল বিজ্ঞপ্তির জন্য পরিকল্পিত ছিল। অবনদিত। আজ, আমি Firebase Firestore-쪽으로 مایগ্রেশন সম্পর্কে বলছি : প্রকল্পের সৃষ্টি, নিরাপত্তা নিয়ম, JS-এ পুনিলেখন, Supabase-এর মৃত কোডের পরিষ্কারকরণ। এবং এই বেছে নেওয়া কেন ডেভেলপমেন্ট দর্শনের একটি বড় দিককে প্রকাশ করে।
- টক
-
[]
দৃশ্য: একটি ফর্ম যা কোনো কিছু সংরক্ষণ করে না
এই সাইট JBake, আমার Gradle প্লাগিন দ্বারা তৈরি করা হয়েছে।bakery. এটি ১০০% স্ট্যাটিক — ব্যাকএন্ড নেই, ডেটাবেস নেই। কিন্তু আমার একটি যোগাযোগ ফর্ম আছে। এই পৃষ্ঠা`contact.html`উপস্থিত, HTML প্রস্তুত (ফিল্ড: নাম, ইমেইল, টেলিফোন, বিষয়, বার্তা, HTML5 যাচাইকরণ, হোনিপট অ্যান্টি‑স্প্যাম), বুটস্ট্র্যাপ স্টাইল প্রয়োগ করা হয়েছে। দৃশ্যমানভাবে, সব شيء সম্পূর্ণরূপে সঠিক।
সাবমিট করার সময় ছাড়া, কোনো কিছু ঘটে না।
const firebaseMock = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.85) {
resolve({ status: 201, message: 'Message stored in Firestore.' });
} else {
reject({ status: 500, message: 'Firestore write failed.' });
}
}, 1500);
});
একটি মক। একটি মিথ্যা প্রতিজ্ঞা। ব্যবহারকারী একটি স্পিনার দেখে, তারপরে একটি বার্তা « বার্তা সফলভাবে পাঠানো হয়েছে ! » দেখায়। কিন্তু ডেটা শূন্যে যায়। কোনো বার্তা কোথাও সংরক্ষিত নয়।
একটি টুটি ফর্মের চেয়ে আরও খারাপ — এটি একটি মিথ্যা বলা ফর্ম।
Supabase-এর ঐতিহ্য
প্রারಂಭিক পরিকল্পনা, যেখানে নথিবদ্ধ`content/draft/integration_formulaire_contact_supabase.adoc`, আশা করছিল :
-
একটি সুপabase ডেটাবেস টেবিলসহ`contacts`এবং Row Level Security
-
একটি দূরবর্তী পদ্ধতির আহ্বান`handle_contact_form`সার্ভার-সাইড
-
একটি SQL ট্রিগার যা একটি গুগল অ্যাপস স্ক্রিপ্ট ওয়েবহুককে 콜 করে
-
Gmail বিজ্ঞপ্তি ইমেইল পাঠায় Google Apps Script
সংশ্লিষ্ট JavaScript কোড এখনও_exists_exists_ আছে`script.js`. একটি শ্রেণী আছে`SupabaseManager`যে গ্লোবাল ভেরিয়েবল দিয়ে একটি Supabase ক্লায়েন্ট শুরু করে`SUPABASE_URL` et SUPABASE_KEY, এবং একটি শ্রেণী`ContactFormHandler`ফর্মের submit ইভেন্ট শুনে এবং কল করে`SupabaseManager.submitContactForm()`.
সমস্যা: এই গ্লোবাল ভেরিয়েবলগুলি আরও ফুটারে ইনজেক্ট করা হয় না। এই`<script src="supabase-js">`এটি সরিয়ে নেওয়া হয়েছে। কোড কল করে`supabase.createClient()`একটি ভ্যারিয়েবলে`supabase`যে আর বিদ্যমান নেই। তাই :
console.error : 'Supabase client library (supabase-js) is not loaded.'
বেশি না ডেটা সংরক্ষণ করা হয়নি, বরং জমির কোডও মরা গেছে।
ফ্যান্টম ডাবল জমা
অন জিনিস খারাপ করতে, একটি আছেনীরব প্রতিযোগিতাএকই ফর্মের দুটি হ্যান্ডলারের মধ্যে :
-
`contact.js`submit‑কে শুন, মক ফায়ারবেস কল কর
-
script.js— মধ্যеме`ContactFormHandler`সাবমিটটি শুনও, ডাকও`SupabaseManager`
দুজনের করে`event.preventDefault() + event.stopPropagation(). যেমন`contact.js`প্রথমে এখানে লোড করা হয়`footer.thyme, এর হ্যান্ডلر প্রথমে সংযুক্ত হয়। এটি প্রসারণ বাধা দেয়।`ContactFormHandler`কখনও ট্রিগার করা হবে না।
এটি এমনকি কোনো সক্রিয় ব্যাগ নয় — এটি একটি জম্বি। একটি কোড যা কখনো চালাতে পারে না।
Failed to generate image: PlantUML preprocessing failed: [From <input> (line 7) ] @startuml skinparam backgroundColor #FEFEFE title প্রারంభিক অবস্থা — যোগাযোগ ফর্ম rectangle "contact.thyme\n(HTML Bootstrap, honeypot)" as FORM rectangle "footer.thyme (Firebase SDK কনফিগারেশন প্লেসহোল্ডার সঙ্গে)" as FOOT ^^^^^ Syntax Error? (Assumed diagram type: activity) @startuml skinparam backgroundColor #FEFEFE title প্রারంభিক অবস্থা — যোগাযোগ ফর্ম rectangle "contact.thyme\n(HTML Bootstrap, honeypot)" as FORM rectangle "footer.thyme (Firebase SDK কনফিগারেশন প্লেসহোল্ডার সঙ্গে)" as FOOT rectangle "contact.js\n(Firebase মক, 85% সাফল্য)" as CONT rectangle "script.js\n(SupabaseManager + ContactFormHandler, মৃত)" as SCRIPT rectangle "ব্যবহারকারী" as USER USER -> FORM : Soumission du formulaire FORM -> CONT : submit event (attaché en premier) CONT -> CONT : preventDefault + stopPropagation CONT -> CONT : mock Promise (1.5s, aléatoire) note right of CONT ⚠️ Aucune donnée stockée end note FORM ..> SCRIPT : submit event (bloqué par stopPropagation) note right of SCRIPT : ❌ Jamais déclenché note right of SCRIPT : ❌ supabase-js non chargé @enduml
Firebase কেন Supabase এর পরিবর্তে ?
মাইগ্রেশন সিদ্ধান্তটি নথিভুক্ত করা হয়েছে`AGENT.adoc`:
Firebase এখন নিম্নোক্ত কারণে বেছে নেওয়া হচ্ছে: ভালো মুক্ত পরিকল্পনা, নেটিভ Firestore, ইন্টিগ্রেটেড Cloud Functions, আরও উপযুক্ত Google ইকোসিস্টেম। বিদ্যমান Supabase বাস্তবায়ন « ⚠️ ত্যাগ করা » হিসাবে চিহ্নিত করা হয়েছে।
বিনামূল্য প্লানের বাইরে, একটি আর্কিটেকচারিক причину আছে। এই সাইটটি গুগল ইকোসিস্টেমে বাস করে : লক্ষ্য সংগ্র�স্থলটি`cheroliv.github.io`, CNAME GitHub Pages-এ নির্দেশ করে, Gradle বিল্ড JGit এর মাধ্যমে GitHub-এ push করে। Google (Firebase) পরিষেবা ব্যবহার করা তৃতীয় পক্ষের (Supabase) পরিষেবা ব্যবহারের চেয়ে বিভাজনের পৃষ্ঠকে কমায়।
Firestore নেটিভ মোডে (Datastore মোড না) আমার মনে থাকা নোএসকিউএল ডকুমেন্ট মডেলেরও আশিকভাবে কাছাকাছি: সংগ্রহ, নথি, টাইপড ফিল্ড, সার্ভার টাইমস্ট্যাম্প, অন্তরীকৃত নিরাপত্তা নিয়ম।
ফেজ ১ : ফায়ারবেস প্রকল্প তৈরি করুন
প্রারম্ভীకరণ
আমার মেশিনে Firebase CLI ইনস্টল করা নেই, তাই আমি ওয়েব কনসোলে যাচ্ছি :
-
যাওয়াhttps://console.firebase.google.com/[Firebase কনসোল]
-
প্রকল্প তৈরি করুন`cheroliv-contact`(অথবা একটি বিদ্যমান প্রকল্প পুনরায় ব্যবহার করুন)
-
ফায়ারস্টোরকে নেটিভ মোডে সক্রিয় করুন (ড্যাটastore নয়)
-
একটি ডেটাবেস অঞ্চলে সৃষ্টি করুন`eur3`(Europe)
আমাদের মতো একটি মিনিমালিস্ট ব্যবহারের জন্য (একই কালেকশন, পাবলিক রাইটিং), নেটিভ মোড সঠিক বাছাই। জটিল Datastore নিয়মের প্রয়োজন নেই।
Firestore নিরাপত্তা নিয়ম
ফর্মটি সর্বজনীন — কেউই একটি বার্তা পাঠাতে পারে। কিন্তু আমি দুর্ব্যবহার সীমিত করতে চাই:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /contact_messages/{messageId} {
// Lecture : admin uniquement (authentifié)
allow read: if request.auth != null;
// Écriture : publique, mais limitée
allow create: if request.auth == null
&& request.resource.data.name is string
&& request.resource.data.name.size() >= 1
&& request.resource.data.name.size() <= 100
&& request.resource.data.email is string
&& request.resource.data.email.matches('.*@.*\\..*')
&& request.resource.data.email.size() <= 254
&& request.resource.data.subject is string
&& request.resource.data.subject.size() >= 3
&& request.resource.data.subject.size() <= 200
&& request.resource.data.message is string
&& request.resource.data.message.size() >= 10
&& request.resource.data.message.size() <= 5000
&& request.resource.data.created_at == request.time
&& request.resource.data.user_agent is string
&& request.resource.data.user_agent.size() <= 500;
}
}
}
প্রধান পয়েন্ট:
-
allow read— শুধুমাত্র প্রমাণীকৃত ব্যবহারকারীরা বার্তাগুলো পড়তে পারে (আমি, Firebase কনসোলের মাধ্যমে) -
allow create— যে কেউ একটি ডকুমেন্ট তৈরি করতে পারে, কিন্তু ফিল্ডের যাচাইyer সঙ্গে -
সার্ভার-পাশের বৈধতা: ন্যূনতম ও অধিকতম আকার, ইমেইল ফরম্যাট,
created_at`সামঞ্জস্য রাখতে হবে`request.time(নকল-বিরোধী) -
`user_agent`ট্রেসযোগ্যতার জন্য পাঠানো হয় (প্রয়োজনীয় নয় কিন্তু উপকারী)
এই বিধিগুলি একটি সাধারণের চেয়ে অধিক কঠোর`allow write: if true;`. এগুলি একজন আক্রমণকারীকে বিশাল পেলোড ইনজেক্ট করতে বা অসংযোজিত ক্ষেত্র তৈরি করতে বাধা দেয়।
ফেজ ২: জমার জাভাস্ক্রিপ্ট পুনরলিখুন
সমঝোতা সরল :
-
ফর্মের ডেটা পড়ুন
-
হনিপট (ক্ষেত্র) যাচাই করুন`hp_name`— যদি এটি পূর্ণ হয়, তাহলে এটি একটি বট, আমরা কিছু পাঠানো না করে সাফল্য সিমুল করি)
-
ডাকানো`addDoc(window.FIREBASE.collection(db, "contact_messages"), {…})`
-
সাফল্য বা ত্রুটি দেখানো
নির্ভরতা `window.FIREBASE
মধ্যে`footer.thyme`, একটি স্ক্রিপ্ট মডিউল Firebase SDK প্রারম্ভ করে এবং একটি গ্লোবাল অবজেক্ট :
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.0/firebase-app.js";
import { getFirestore, collection, addDoc, serverTimestamp }
from "https://www.gstatic.com/firebasejs/11.6.0/firebase-firestore.js";
const firebaseConfig = { /* valeurs réelles */ };
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
window.__FIREBASE__ = { db, collection, addDoc, serverTimestamp };
</script>
মডিউল স্ক্রিপ্টগুলো আগে চালানো হয়`DOMContentLoaded`, অতেব`window.FIREBASE`এটি হ্যান্ডলারের সময় গ্যারান্টিত উপলব্ধ`contact.js`শুরু হয়। সতর্কতায়, আমি ৫ সেকেন্ডের পোলিং যোগ করি যদিও যদি CDN ধীর হয়।
নতুন `contact.js
document.addEventListener('DOMContentLoaded', function () {
'use strict';
const form = document.getElementById('contact-form');
if (!form) return;
const submitButton = form.querySelector('button[type="submit"]');
const successMessage = document.getElementById('contact-success-message');
const errorMessage = document.getElementById('contact-error-message');
// Éléments de validation
const nameInput = form.querySelector('input[name="name"]');
const emailInput = form.querySelector('input[name="email"]');
const phoneInput = form.querySelector('input[name="phone"]');
const subjectInput = form.querySelector('input[name="subject"]');
const messageInput = form.querySelector('textarea[name="message"]');
const honeypotInput = form.querySelector('input[name="hp_name"]');
/**
* Attend que window.__FIREBASE__ soit disponible.
* Timeout de 5 secondes — si le CDN Firebase est lent, on abandonne.
*/
function waitForFirebase(timeoutMs = 5000) {
return new Promise((resolve, reject) => {
if (window.__FIREBASE__) {
resolve(window.__FIREBASE__);
return;
}
const start = Date.now();
const interval = setInterval(() => {
if (window.__FIREBASE__) {
clearInterval(interval);
resolve(window.__FIREBASE__);
} else if (Date.now() - start > timeoutMs) {
clearInterval(interval);
reject(new Error('Firebase SDK non disponible après timeout'));
}
}, 100);
});
}
// --- Validation (identique à l'existant) ---
function validateForm() {
nameInput.setCustomValidity('');
emailInput.setCustomValidity('');
if (phoneInput) phoneInput.setCustomValidity('');
subjectInput.setCustomValidity('');
messageInput.setCustomValidity('');
if (nameInput.value.trim().length < 1) {
nameInput.setCustomValidity('Veuillez saisir votre nom.');
}
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(emailInput.value.trim())) {
emailInput.setCustomValidity('Veuillez saisir une adresse email valide.');
}
if (phoneInput && phoneInput.value.trim() !== '') {
const phonePattern = /^\d{10,15}$/;
if (!phonePattern.test(phoneInput.value.trim())) {
phoneInput.setCustomValidity('Veuillez saisir un numéro valide (10 à 15 chiffres).');
}
}
if (subjectInput.value.trim().length < 3) {
subjectInput.setCustomValidity('Veuillez saisir un sujet (3 caractères minimum).');
}
if (messageInput.value.trim().length < 10) {
messageInput.setCustomValidity('Veuillez saisir un message (10 caractères minimum).');
}
form.classList.add('was-validated');
return form.checkValidity();
}
// --- Handler de soumission ---
form.addEventListener('submit', async function (event) {
event.preventDefault();
event.stopPropagation();
if (!validateForm()) return;
// Honeypot : si rempli, simuler un succès sans rien envoyer
if (honeypotInput && honeypotInput.value.trim() !== '') {
successMessage.style.display = 'block';
form.reset();
form.classList.remove('was-validated');
return;
}
// UI : état d'envoi
submitButton.disabled = true;
submitButton.innerHTML = `
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Envoi en cours...
`;
successMessage.style.display = 'none';
errorMessage.style.display = 'none';
try {
const fb = await waitForFirebase();
const messagesCollection = fb.collection(fb.db, 'contact_messages');
await fb.addDoc(messagesCollection, {
name: nameInput.value.trim(),
email: emailInput.value.trim(),
phone: phoneInput ? phoneInput.value.trim() : '',
subject: subjectInput.value.trim(),
message: messageInput.value.trim(),
created_at: fb.serverTimestamp(),
user_agent: navigator.userAgent.substring(0, 500)
});
successMessage.style.display = 'block';
form.reset();
form.classList.remove('was-validated');
} catch (error) {
console.error('Erreur Firestore:', error);
errorMessage.style.display = 'block';
} finally {
submitButton.disabled = false;
submitButton.innerHTML = `
<i class="bi bi-send me-2"></i>
Envoyer le Message
`;
}
}, false);
});
মক-এর তুলনায় পরিবর্তনগুলো :
-
waitForFirebase()— টাইমআউট সহ পোলিং, CDN ধীর থাকলেও দৃঢ় -
honeypot— যদি লুকানো ক্ষেত্র`hp_name`পূর্ণ হয়েছে, Firestore-এ একটি নিশ্চিত সাফল্য সিমুলেট করা। বট ধরে যে সে সাফল্য পেয়েছে কিন্তু কিছুই সংরক্ষণ করা হয়নি। -
addDoc(collection, {…})— সত্য Firestore কল দিয়ে`serverTimestamp()` etuser_agent -
ত্রুটি হ্যান্ডেলিং সঙ্গে`try/catch`অ্যাসিঙ্ক্রোনাস
-
সাফ-সুচির`finally`(বোতামের পুনঃস্থাপন)
|
কেন`user_agent`? এইচ্ছিক, কিন্তু ডায়াগনস্টিকের জন্য উপকারী। যদি কোন অদ্ভুত বার্তা আসে, তা বুঝতে পouvoir যে এটি একটি ডেস্কটপ ব্রাউজার, মোবাইল ব্রাউজার, অথবা curl স্ক্রিপ্ট থেকে আসছে তা শ্রেণীবিন্যাসে সাহায্য করে। |
ফেজ ৩: Supabase-তে মৃত কোড পরিষ্কার করুন
`script.js`এটি ২৫০ লাইন মৃত কোড ধারণ করে:
-
SupabaseManager(লাইন 417-481) — 65 লাইন -
ContactFormHandler(লাইন ৪৯০-৫৫১) — ৬২ লাইন -
প্রারম্ভিক ব্লক (লাইন ৬৪৫-৬৫৪) — ১০ লাইন
মোট : ~140 লাইন মুছে ফেলা দরকার
ব্লক`DOMContentLoaded`একটি তৈরি করে`SupabaseManager`এরপর একটি`ContactFormHandler`ফর্মে সংযুক্ত। উপরে ব্যাখ্যা করা মতো, এই কোড কখনো চালু হয় না (ব্লক করা`contact.js`), এবং যদি এটি চালিত হত, তবেও এটি ব্যর্থ হত (সাপবেস SDK লোড করা হয়নি).
আমি মুছে ফেলছি :
-
শ্রেণি`SupabaseManager`
-
শ্রেণি`ContactFormHandler`
-
ইনিশিয়ালাইজেশন ব্লকে`DOMContentLoaded`(লাইন ৬৪৫-৬৫৪)
Le reste de script.js`অখণ্ড :`ThemeManager, ScrollToTopButton, MobileMenuManager, SmoothScrollWithOffset, NavbarHeightUpdater, DynamicNavbarBreakpoint, CodeBlockManager, TooltipManager, PhoneInputManager.
ফেজ 4 : ফুটার কনফিগার করুন
`footer.thyme`এটায় ইতিমধ্যে ফায়ারবেস বয়লারপ্লেট আছে কিন্তু placeholder মান সহ। আমি প্রতিস্থাপন করছি:
const firebaseConfig = {
apiKey: "REMPLACER_PAR_VOTRE_API_KEY",
authDomain: "REMPLACER_PAR_VOTRE_AUTH_DOMAIN",
projectId: "REMPLACER_PAR_VOTRE_PROJECT_ID",
storageBucket: "REMPLACER_PAR_VOTRE_STORAGE_BUCKET",
messagingSenderId: "REMPLACER_PAR_VOTRE_SENDER_ID",
appId: "REMPLACER_PAR_VOTRE_APP_ID"
};
বাস্তব মানগুলি পুনরুদ্ধার থেকেপ্রজেক্ট সেটিংস > জেনারেল > আপনার অ্যাপস > ওয়েব অ্যাপফায়ারবেস কনসোলে।
মানগুলো সংবেদনশীল`apiKey`Firebase-এ ডিজাইনের মোতাবিক সার্বজনিক, কিন্তু আমি পছন্দ করি না তারদের স্পষ্টরূপে কামিট করার) আমি他们的 … -এ সংরক্ষণ করি`site.yml`(এখনও মধ্যে`.gitignore`) এবং প্লাগইন`bakery`তেগুলোকে টেমপ্লেটে বিল্ড পাশে যুক্তি যোগ করে ইন্জেক্ট করে।
এখনও, আমি সরাসরি তাদেরকে ভিতরে রাখছি`footer.thyme`— বিল্ড`./gradlew serve`এগুলো স্থানীয়ভাবে লোড করবে। ডিপ্লয়মেন্টের সময়, আমি ইনজেকশন(to)(move)করব__(direction)`site.yml`অথবা একটি Gradle ভেরিয়েবলে।
|
L'`apiKey`Firebase নানাএকটি রহস্য। এটি ডিজাইনের মাধ্যমে সর্বজনীন। আপনার ডেটা রক্ষা করে, সেগুলোফায়ারস্টোর নিরাপত্তা নিয়ম, এপিআই কী নয়। এটিকে একটি …-এ রাখবেন না`.env`সার্ভার-সাইডে লোড করা হয়েছে — এটি ব্রাউজরে প্রকাশ করার জন্য নিশ্চিত করা হয়েছে। |
ফেজ 5 : শেষ আর্কিটেকচার
Failed to generate image: PlantUML preprocessing failed: [From <input> (line 8) ]
@startuml
skinparam backgroundColor #FEFEFE
title শেষ আর্কিটেকচার — ফায়ারবেস যোগাযোগ ফর্ম
actor Utilisateur as USER
package "ব্রাউজার" #E8F5E9 {
rectangle "contact.thyme
^^^^^
Syntax Error? (Assumed diagram type: component)
@startuml
skinparam backgroundColor #FEFEFE
title শেষ আর্কিটেকচার — ফায়ারবেস যোগাযোগ ফর্ম
actor Utilisateur as USER
package "ব্রাউজার" #E8F5E9 {
rectangle "contact.thyme
(HTML, মধুমাকর)" as FORM
rectangle "contact.js
( যাচাই + Firestore )" as CONT
rectangle "footer.thyme\n(ফায়ারবেস SDK প্রারম্ভ)" as FOOT
}
cloud "ফায়ারবেস" #BBDEFB {
rectangle "Firestore
(contact_messages)" as FS
}
FORM --> CONT : submit event
CONT --> FOOT : window.__FIREBASE__.addDoc()
FOOT --> FS : Insert document\n(règles de sécurité validées)
note right of FS
Firestore Rules :
- create: public, validé
- read: auth uniquement
end note
@enduml
এয়া মাইগ্রেশন ডগফুডিং এর বRGে যা Bale
এই সাইটটি আমার নিজের Gradle প্লাগইন দ্বারা তৈরি করা হয়েছে`bakery`. সাম্পর্ক ফর্মটি সাইটের ভেতরে বসবাস করে। Supabase → Firebase মাইগ্রেশন নথিভুক্ত করা হয়েছে`AGENT.adoc`, ব্যাকলগে আলোচিত, মাধ্যমে পরীক্ষিত`./gradlew serve`, এবং এটি একটি ব্লগ পোস্ট তৈরি করে (যেটি আপনি পড়ছেন)।
এটি শুধু ডগফুডিং। সাইটটি প্লাগইনটির পণ্য, প্লাগইনটি ডেভেলপারের পণ্য, ডেভেলপারটি প্রক্রিয়াটি নিজের সাইটে নথিভুক্ত করে।
লুপটি বন্ধ হয়েছে।
মাস ধরে একটি মককে টানতে থাকা (ফর্মটি নীরবে মিথ্যা বলっていた পূর্ণাঙ্গ সেশন) আমাকে কিছু বোঝাতে সাহায্য করে: একজন ব্যক্তিগত স্ট্যাটিক সাইটের ব্যাকলগ কখনো «শেষ» নয়। সর্বদা একটি অগ্রাধিকারকৃত ইউএস, সর্বদা একটি ড্রাফট আর্টিকেল, সর্বদা একটি টেমপ্লেটে মন্তব্যুক্ত অংশ। অনুশাসন মানে সবকিছু শেষ করা নয় — বরং ব্যবহারকারী által দেখতে পাওয়ার যোগ্য কাজটি শেষ করা।
একটি ভাঙা যোগাযোগ ফর্ম, এটা কোনো ফর্ম নেই ভালো। এটা একটি অপূত প্রতিজ্ঞা।
পরিবর্তনের সারসংক্ষেপ
ফাইল |
শোধন |
প্রভাব |
|
আর্টিকেলের সৃষ্টি |
ডকুমেন্টেশন |
|
পুনালেখন (mock → বাস্তব Firestore) |
কর্মক্ষম |
|
অপসারণ SupabaseManager + ContactFormHandler + init bloc |
সফাই |
|
প্রতিস্থাপন কনফিগ প্লেসহোল্ডার → বাস্তব মান |
কনফিগারেশন |
আগামী ধাপ (backlog)
-
বিজ্ঞপ্তি ইমেইল: একটি ক্লাউড ফাংশন`onCreate`উপর`contact_messages`কে SendGrid- এর মাধ্যমে একটি মেইল পাঠায়। ফর্ম ডেটা সংরক্ষণ করা হয়, কিন্তু আমি বিজ্ঞপ্তি পাচ্ছি না। মাঝারি প্রাধান্য — বার্তাগুলি Firebase কনসোলে দেখা যায়।
-
ক্লায়েন্ট সাইড রেট লিমিটিং: একটি localStorage টাইমস্ট্যাম্প যোগ করে মাল্টিপল সাবমিশন র্যাফলে বাধা দেওয়া উচিত। হনি পট নাইশ বটদের ব্লক করে, একটি রেট লিমিটার কিছুটা চালাক বটদের ব্লক করতে পারে।
-
পরীক্ষা: একটি Playwright টেস্ট যা ফর্মটিকে জমায় এবং যাচাই করে যে ডকুমেন্ট Firestore-এ দৃশ্যমান হয়। এখন, আমি ম্যানুয়ালি টেস্ট করছি via`./gradlew serve`.