কম্পিউটার টিউটোরিয়াল

Firebase, Upstash এবং SvelteKit দিয়ে একটি ওপেন-সোর্স JIRA ক্লোন তৈরি করা

এই পোস্টে, আমি আলোচনা করব কীভাবে আমি Upstash, SvelteKit, এবং Firebase Storage ব্যবহার করে Jira Kanban Board-এর একটি ওপেন-সোর্স অ্যাল্টারনেটিভ তৈরি করেছি।

Firebase, Upstash এবং SvelteKit দিয়ে একটি ওপেন-সোর্স JIRA ক্লোন তৈরি করা

আমরা কী ব্যবহার করব

  • SvelteKit (UI এবং API রুট)
  • Upstash (CRUD অপারেশন)
  • Tailwind CSS (স্টাইলিং)
  • Firebase Storage (এসেট [ছবি, পিডিএফ, ইত্যাদি] স্টোরেজ)
  • Auth.js দ্বারা SvelteKit Auth

আপনার কী কী লাগবে

  • একটি Upstash অ্যাকাউন্ট ডেটাবেস তৈরি করার জন্য
  • একটি Firebase অ্যাকাউন্ট একটি স্টোরেজ কন্টেইনার তৈরি করার জন্য
  • একটি Google OAuth 2.0 সেটআপ OAuth শংসাপত্র পাওয়ার জন্য

Upstash Redis সেট আপ করা

একবার আপনি Upstash অ্যাকাউন্ট তৈরি করে লগ ইন করলে, আপনি Redis ট্যাবে যাবেন এবং একটি ডেটাবেস তৈরি করবেন।

Firebase, Upstash এবং SvelteKit দিয়ে একটি ওপেন-সোর্স JIRA ক্লোন তৈরি করা Firebase, Upstash এবং SvelteKit দিয়ে একটি ওপেন-সোর্স JIRA ক্লোন তৈরি করা

আপনার ডেটাবেস তৈরি হলে, আপনি Details ট্যাবে যাবেন। নিচে স্ক্রোল করুন যতক্ষণ না Connect your database সেকশনটি পাওয়া যায়। কন্টেন্ট কপি করুন এবং কোথাও সুরক্ষিত জায়গায় সংরক্ষণ করুন।

Firebase, Upstash এবং SvelteKit দিয়ে একটি ওপেন-সোর্স JIRA ক্লোন তৈরি করা

এছাড়া, নিচে স্ক্রোল করুন যতক্ষণ না REST API সেকশনটি পাওয়া যায় এবং .env বাটনটি সিলেক্ট করুন। কন্টেন্ট কপি করুন এবং কোথাও সুরক্ষিত জায়গায় সংরক্ষণ করুন।

Firebase, Upstash এবং SvelteKit দিয়ে একটি ওপেন-সোর্স JIRA ক্লোন তৈরি করা

প্রজেক্ট সেট আপ করা

সেট আপ করতে, শুধু অ্যাপ রিপো ক্লোন করুন এবং এই টিউটোরিয়াল ফলো করুন সেটার সব কিছু শিখতে। প্রজেক্ট ফর্ক করতে, চালান:

git clone https://github.com/rishi-raj-jain/jira-sveltekit-firebase-storage-upstash-starter
cd jira-sveltekit-firebase-storage-upstash-starter
npm install

রিপো ক্লোন করার পর, আপনি একটি .env ফাইল তৈরি করবেন। আপনি উপরের ধাপগুলো থেকে সংরক্ষণ করা আইটেমগুলো যোগ করবেন।

এটি কিছু এইরকম দেখাবে:

# .env
# Google OAuth 2.0 সেটআপ থেকে প্রাপ্ত
# https://support.google.com/cloud/answer/6158849?hl=en
GOOGLE_ID="..."
GOOGLE_SECRET="..."
# SvelteKit Auth
AUTH_SECRET="..."
# একটি র‍্যান্ডম ৩২ ক্যারেক্টার স্ট্রিং
AUTH_TRUST_HOST=true
# উপরের ধাপগুলো থেকে Upstash থেকে প্রাপ্ত
UPSTASH_REDIS_REST_URL="your_upstash_redis_rest__url_from_above"
UPSTASH_REDIS_REST_TOKEN="your_upstash_redis_rest__token_from_above"
// firebase-adminsdk.json
// আপনার ফায়ারবেস প্রজেক্ট থেকে প্রাপ্ত ফায়ারবেস কনফিগারেশন
// ফায়ারবেস কনফিগারেশন সম্পর্কে আরও পড়ুন
// https://firebase.google.com/docs/web/learn-more#config-object
{ "type": "...", "project_id": "...", "private_key_id": "...", "private_key": "...", "client_email": "...", "client_id": "...", "auth_uri": "...", "token_uri": "...", "auth_provider_x509_cert_url": "...", "client_x509_cert_url": "...", "universe_domain": "...", "storageBucket": "..." }

এই ধাপগুলো পূরণ করার পর, আপনাকে নিচের কমান্ড ব্যবহার করে লোকাল এনভায়রনমেন্ট শুরু করতে পারবেন:

npm run dev

রিপোজিটরি স্ট্রাকচার

এটি প্রজেক্টের মেইন ফোল্ডার স্ট্রাকচার। আমি লাল বর্গে চিহ্নিত করেছি যে ফাইলগুলো এই পোস্টে আরও আলোচনা করা হবে যা CRUD অপারেশন, SvelteKit Auth, এবং ফাইল আপলোড হ্যান্ডলার সম্পর্কিত, সেগুলো যেখানে রেফারেন্স করা হয়েছে।

Firebase, Upstash এবং SvelteKit দিয়ে একটি ওপেন-সোর্স JIRA ক্লোন তৈরি করা

SvelteKit-এর Edge ফাংশনকে ইউজার অথেন্টিকেশন দিয়ে প্রোটেক্ট করা

Auth.js-র টিমের একটি চমৎকার কাজ ने SvelteKit-এ Auth-কে একটি স্মুথ অপারেশন बना দিয়েছে। প্রজেক্টটি ইমপ্লিমেন্ট করে:

Google OAuth 2.0 ব্যবহার করে সব পেজে অথরাইজেশন

SvelteKit-এর সার্ভার হুক ব্যবহার করে, আমরা সকল ইনকামিং রিকোয়েস্টে (যেকোনো পেজে) Auth এনফোর্স করি:

// File: @/hooks.server.ts
import Google from "@auth/core/providers/google";
import { SvelteKitAuth } from "@auth/sveltekit";
import type { Handle } from "@sveltejs/kit";
import { GOOGLE_ID, GOOGLE_SECRET } from "$env/static/private";
// আরও পড়ুন
// https://kit.svelte.dev/docs/hooks#server-hooks-handle
export const handle = SvelteKitAuth({ // @ts-ignore providers: [Google({ clientId: GOOGLE_ID, clientSecret: GOOGLE_SECRET })], }) satisfies Handle;

SvelteKit-এর সার্ভার লোকালস ব্যবহার করে Edge ফাংশন(গুল)য় অথরাইজেশন

SvelteKit-এর সার্ভার লোকালস ব্যবহার করে, আমরা যেকোনো সার্ভার-সাইড-অনলি অপারেশনে অপ্ট-ইন করতে পারি চেক করতে যে ইউজার অথেন্টিকেটেড কিনা। নিচে একটি উদাহরণ দেয়া হলো একটি নতুন ইস্যু তৈরি করার সময় ইউজার অথেন্টিকেটেড কিনা তা যাচাই করতে:

import { json } from '@sveltejs/kit'
import { isAuth } from '@/lib/auth'
import type { RequestEvent } from './$types'
import { getTask, getTasks } from '@/lib/issues'
import type { LayoutServerLoadEvent } from '../routes/$types'
import type { RequestEvent, ServerLoadEvent } from '@sveltejs/kit'
// ইউজার সেশন পান যদি ইভেন্ট লোকালসে উপলব্ধ থাকে
const isAuth = async (event: LayoutServerLoadEvent | ServerLoadEvent | RequestEvent) => {
  const session = await event.locals.getSession()
  if (session?.user?.image) { return { session } }
  return false
}
export async function GET(event: RequestEvent) {
  // যদি ইউজার অথেন্টিকেটেড না হয়, ৪০৩ থ্রো করুন
  if (!(await isAuth(event))) { return new Response(undefined, { status: 403 }) }
  const url = event.url
  const idSearchParam = url.searchParams.get('id')
  if (idSearchParam) { const res = await getTask(idSearchParam); return json(res) }
  else if (url.searchParams.get('all')) { const res = await getTasks(); return json(res) }
  return new Response(JSON.stringify({ code: 0, error: 'Invalid Request.' }), { status: 400, headers: { 'content-type': 'application/json' } })
}

Upstash Redis দিয়ে Issue(s) CRUD অপারেশন

এই সেকশনে, আমরা গভীরে যাব কীভাবে Kanban বোর্ডের প্রতিটি ইস্যুর জন্য ডেটা ফেচিং, আপডেটিং এবং ডিলিশন করা হয়। আমরা Upstash DB (via @upstash/redis) ব্যবহার করে ডেটা ফেচ, ডিসপ্লে এবং রিফ্রেশ করি।

getTask: ইস্যু ডেটা ফেচ করার ফাংশন

getTask ফাংশনটি Upstash-এর hget ব্যবহার করে id-কে কি হিসেবে ব্যবহার করে একটি API রিকোয়েস্ট করে Upstash-এ সম্পর্কিত ইস্যু ডেটার জন্য, যা একটি ইউনিক id- দ্বারা আইডেন্টিফাইড। যদি वह ইস্যু উপস্থিত না থাকে (বা একটি এরর হয়), ফাংশনটি একটি অবজেক্ট রিটার্ন করতে সেট করা হয়েছে { code: 0 } 그와 সাথে যাতে ইউজারকে SvelteKit-এর ডাইনামিক রুটে অটোমেটিক্যালি ৪০৪ (ইস্যু নট ফাউন্ড) এ রিডাইরেক্ট করা যায়।

type Task = { [key: string]: any } | null;
// Get Issue Data
// File: @/lib/issues/get.ts
export async function getTask(id: string) {
  try {
    const redis = (await import("../upstash/setup")).default;
    const task: Task = await redis.hget("issues", id);
    if (!task) { return { code: 0, error: "No such issue found.", }; }
    return { ...task, code: 1 };
  } catch (e: any) {
    const error = e.message || e.toString();
    console.log(error);
    return { code: 0, error, };
  }
}

সাদৃশ্য, বাকি CRUD অপারেশনগুলো নিম্নরূপ:

// Create Issue
// File: @/lib/issues/create.ts
export async function createTask(info: any) {
  try {
    const redis = (await import("../upstash/setup")).default;
    const id = Math.random().toString().slice(2) + new Date().getUTCMilliseconds();
    await redis.hset("issues", { [id]: info });
    return { code: 1, id, message: "Issue Created Succesfully ✅" };
  } catch (e: any) {
    const error = e.message || e.toString();
    console.log(error);
    return { code: 0, error, };
  }
}
// Delete Issue
// File: @/lib/issues/delete.ts
export async function deleteTask(id: string) {
  try {
    const redis = (await import("../upstash/setup")).default;
    await redis.hdel("issues", id);
    return { code: 1, message: "Deleted Succesfully!" };
  } catch (e: any) {
    const error = e.message || e.toString();
    console.log(error);
    return { code: 0, error, };
  }
}
// Update Issue Data
// File: @/lib/issues/update.ts
export async function updateTask(info: any, id: string) {
  try {
    const redis = (await import("../upstash/setup")).default;
    if (id) {
      const task = await redis.hget("issues", id);
      if (task) {
        await redis.hset("issues", { [id]: info });
        return { code: 1, message: "Updated Successfully" };
      }
    }
    return { code: 0, error: "No such issue was found.", };
  } catch (e: any) {
    const error = e.message || e.toString();
    console.log(error);
    return { code: 0, error, };
  }
}

রেট লিমিটিং

এজ-এ রেট-লিমিটিং ইমপ্লিমেন্ট করতে, আমরা Upstash Redis ডেটাবেস ক্লায়েন্ট এবং একটি রেট লিমিটার লাইব্রেরি @upstash/ratelimit ব্যবহার করি।

// রেটলিমিটিং রেফারেন্স ফাংশন
// File: @/lib/upstash/ratelimit.ts
import { Ratelimit } from "@upstash/ratelimit";
import redis from "./setup";
export const ratelimit = { upload: new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(2, "60s"), }), issues: new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(5, "60s"), }), };

রেট লিমিটিং ব্যবহার করে, আমি নিম্নলিখিতগুলো অর্জন করতে পেরেছি:

A. প্রতি ইউজার প্রতি মিনিট ইস্যু ক্রিয়েশন লিমিট করা

রেট লিমিটিং ব্যবহার করে, আমি প্রতি অথেন্টিকেটেড ইউজার প্রতি মিনিটে পাঁচটি ইস্যু ক্রিয়েশন লিমিট করতে পেরেছি। আমরা অথেন্টিকেটেড ইউজারের ইমেইলের উপর ভিত্তি করে এই রেট লিমিট এনফোর্স করতে পারছি।

// File: @/routes/api/issue/+server.ts
// Issue Creation POST API SvelteKit Handler
import { ratelimit } from "@/lib/upstash/ratelimit";
export async function POST(event: RequestEvent) {
  const user = await isAuth(event);
  if (!user) { return new Response(undefined, { status: 403, }); }
  if (user.session.user?.email) {
    // এজ-এ অথেন্টিকেটেড ইউজারের ইমেইল দেখুন
    // রেট লিমিট: ৫টি ইস্যু ক্রিয়েশন প্রতি মিনিট
    const result = await ratelimit.issues.limit(user.session.user.email);
    if (!result.success) { return new Response( JSON.stringify({ code: 0, error: `You can't create more than 5 issues per minute.` }), { status: 403, headers: { "content-type": "application/json", }, }, ); }
    const { info } = await event.request.json();
    const res = await createTask(info);
    return json(res);
  }
  return new Response(undefined, { status: 403, }); }

B. প্রতি ইউজার প্রতি ইস্যু প্রতি মিনিট ফাইল আপলোড লিমিট করা

রেট লিমিটিং ব্যবহার করে, আমি প্রতি অথেন্টিকেটেড ইউজার প্রতি টাস্ক প্রতি মিনিটে ২টি ফাইল আপলোড লিমিট করতে পেরেছি। আমরা অথেন্টিকেটেড ইউজারের ইমেইল এবং টাস্কের ID-এর উপর ভিত্তি করে এই রেট লিমিট এনফোর্স করতে পারছি। যখন আপলোড সফলভাবে সম্পন্ন হয়, আমরা Upstash DB-এ টাস্কটি আপডেট করি ফাইল URL অ্যাপেন্ড করে।

// File: @/routes/api/content/+server.ts
// File Upload POST API SvelteKit Handler
import { ratelimit } from "@/lib/upstash/ratelimit";
export async function POST(event: RequestEvent) {
  // User Authentication Code
  if (user.session.user?.email) {
    // ইউজার, টাস্ক আইডি এবং ফাইল আপলোড হয়েছে কিনা ভ্যালিডেট করুন
    // এজ-এ অথেন্টিকেটেড ইউজারের ইমেইল এবং টাস্কের আইডি দেখুন
    // রেট লিমিট: ২টি আপলোড প্রতি মিনিট
    const result = await ratelimit.upload.limit( `${user.session.user.email}_${taskID}`, );
    if (!result.success) { return new Response( JSON.stringify({ code: 0, error: `You can't upload more than 2 files per issue per minute.` }), { status: 403, headers: { "content-type": "application/json", }, }, ); }
    // File upload code
    // কিভাবে ফাইল আপলোড হ্যান্ডেল হচ্ছে তা দেখতে ব্লগ পড়ুন
  }
  return new Response(undefined, { status: 403, }); }

Firebase Storage দিয়ে ফাইল আপলোড এবং ডাউনলোড হ্যান্ডল করা

এই সেকশনে, আমরা গভীরে যাব কীভাবে একটি ইস্যুর ফাইল আপলোড এবং ডাউনলোড সিকিউর এবং অথেন্টিকেটেড ম্যানারে SvelteKit-এর এজ-এ হ্যান্ডেল করা হয়। আমরা Firebase (v9) Storage ব্যবহার করি ফাইল ফেচ এবং আপলোড করার জন্য।

ওহ, কিন্তু কেন Cloudflare R2 স্টোরেজের জন্য নয়?

যদিও আমি Cloudflare R2-এর ফ্রি স্টোরেজ প্ল্যান এবং এর সুবিধাগুলোর জন্য কমিউনিটি অ্যাডভোকেসি খুব দেখেছি, একটা জিনিস যা আমাকে বিরক্ত করলো সেটা হলো Cloudflare-এর 손에 আমার ক্রেডিট কার্ড দিতে হয় সিস্টেম ট্রাই করার আগে। এই বিষয়টি আমাকে অন্যান্য স্টোরেজ সমাধান সম্পর্কে চিন্তা করতে বাধ্য করলো, এবং আমি Firebase Storage-এ পৌঁছেলাম যা আমাকে ৫ GB ফ্রি স্টোরেজ অফার করে, এবং যদি আমি সেটা এক্সিড করি, আমার সার্ভিস বন্ধ হয়ে যাবে বদলে আমার অনুমতি ও জানার বিনা আমার ক্রেডিট কার্ড চার্জ করার।

SvelteKit Edge ফাংশন Firebase Storage-এ ফাইল আপলোড করার জন্য

নিচের Edge ফাংশনে, আমরা যেকোনো POST রিকোয়েস্ট ইভেন্টে দেখছি এবং যদি ইউজার অথেন্টিকেটেড হয়, আমরা ইভেন্টের formData থেকে taskID এবং file পাই। সেটা হলে, আমরা আরও ইভ্যালুয়েট করি ফাইল সাইজ ৫ MB-এর নিচে কিনা। একবার সকল পূর্বশর্ত হলে, আমরা একটি ইউনিক ID তৈরি করি, এবং তারপর ফায়ারবেসের একটি রেফারেন্স তৈরি করি সেই ইউনিক ফোল্ডারের যেখানে ফাইল আপলোড হবে। জেনে ফাইল ফায়ারবেসে আপলোড হয়, এটি আমাদের একটি URL রিটার্ন করে যা আপলোড করা ফাইল অ্যাক্সেস করতে ব্যবহার করা যায়। আমরা এই ইউনিক URL কে ইস্যুর ডেটার files কি-এ অ্যাপেন্ড করি।

// File: @/routes/api/content/+server.ts
// File Upload POST API SvelteKit Handler
import { initializeApp } from "firebase/app";
import { getDownloadURL, getStorage, ref, uploadBytes } from "firebase/storage";
import fireBaseConfig from "../../../../firebase-adminsdk.json";
export async function POST(event: RequestEvent) {
  // User Authentication Code
  if (user.session.user?.email) {
    const app = initializeApp(fireBaseConfig);
    const storage = getStorage(app);
    const data = await event.request.formData();
    const taskID = data.get("taskID");
    const file = data.get("file");
    // ...Validate User, Task ID and if a file is uploaded
    // ...Rate Limiting Code
    // File Size Restriction(s)
    if (file.size > 5 * 1024 * 1024) { return new Response( JSON.stringify({ code: 0, error: "File size exceeds the limit of 5 MB." }), { status: 400, headers: { "content-type": "application/json", }, }, ); }
    // Start File Upload Code
    try {
      // Create a unique ID
      const fileId = uuidv4();
      // If uploaded is not a File type
      if (!(file instanceof File)) return;
      // Create a ref to firebase storage
      const storageRef = ref(storage, `uploads/${fileId}/${file.name}`);
      // Obtain the arrayBuffer of the file uploaded
      const fileBuffer = await file.arrayBuffer();
      // Upload file to Firebase Storage in bytes using Uint8Array
      const { metadata } = await uploadBytes( storageRef, new Uint8Array(fileBuffer), );
      const { fullPath } = metadata;
      // No fullPath is received, the API errored out
      if (!fullPath) { return new Response( JSON.stringify({ code: 0, error: `<span>There was some error while uploading the file.</span> <span class="mt-1 text-xs text-gray-500">Report an issue with the current URL that you are on and with the code XXX.</span>` }), { status: 403, headers: { "content-type": "application/json", }, }, ); }
      // If a file is uploaded successfully, append the file to list of attachments to the issue's data
      const { code, ...taskValues } = await getTask(taskID);
      if (code === 1) {
        if (taskValues) {
          if (taskValues.hasOwnProperty("files")) { taskValues["files"].push( `https://storage.googleapis.com/${storageRef.bucket}/${storageRef.fullPath}`, ); }
          else { taskValues["files"] = [ `https://storage.googleapis.com/${storageRef.bucket}/${storageRef.fullPath}`, ]; }
        }
        // Update the task's data in Upstash
        await updateTask(taskValues, taskID);
      }
      return json({ code: 1, message: "Uploaded Successfully", });
    } catch (error) {
      return new Response( JSON.stringify({ code: 0, error: error.message || error.toString() }), { status: 403, headers: { "content-type": "application/json", }, }, );
    }
  }
  return new Response(undefined, { status: 403, }); }

SvelteKit Edge ফাংশন Firebase Storage থেকে ফাইলের পাবলিক URL ডাউনলোড করার জন্য

আপনি যেহেতু মনে করবেন, আমরা ফায়ারবেস দ্বারা রিটার্ন করা ইউনিক URL কে ইস্যুর files কি-এ অ্যাপেন্ড করেছিলাম। আমরা সেই ইউনিক URL কে GET রিকোয়েস্টে image প্যারামিটার হিসেবে পাই SvelteKit-এর Edge ফাংশনে মূল ফাইল রিট্রিভ করার জন্য। আমরা firebase-এর লাইব্রেরি থেকে getDownloadURL ফাংশন ব্যবহার করি মূল মিডিয়ার পাবলিক URL পাওয়ার জন্য।

// File: @/routes/api/content/+server.ts
// File Upload GET API SvelteKit Handler
import { initializeApp } from "firebase/app";
import { getDownloadURL, getStorage, ref, uploadBytes } from "firebase/storage";
import fireBaseConfig from "../../../../firebase-adminsdk.json";
export async function GET(event: RequestEvent) {
  if (!(await isAuth(event))) { return new Response(undefined, { status: 403, }); }
  const url = event.url;
  const image = url.searchParams.get("image");
  if (image) {
    try {
      const app = initializeApp(fireBaseConfig);
      const storage = getStorage(app);
      const fileRef = ref(storage, image);
      const imagePublicURL = await getDownloadURL(fileRef);
      return json({ code: 1, image: imagePublicURL });
    } catch (error) {
      return new Response( JSON.stringify({ code: 0, error: error.message || error.toString() }), { status: 500, headers: { "content-type": "application/json", }, }, );
    }
  }
  return new Response(JSON.stringify({ code: 0, error: "Invalid Request." }), { status: 400, headers: { "content-type": "application/json", }, }); }

আপনি ইতিমধ্যে ভাবছেন, একাধিক মিডিয়া আপলোড করা যেতে পারে, তাই ইমেজ বনাম ভিডিওর טרিভিয়াল কেস হ্যান্ডল করার জন্য, আমি ফ্রন্টএন্ডে নিচের if else যোগ করেছি:

<!-- File: @/routes/issue/[slug]/+page.svelte -->
{#each fieldFiles as file}
  <div class="mt-8 w-full border border-white/25 p-3">
    {#if /\.(mp4|mov|mkv)/i.test(file)}
      <video class="h-auto w-full" src="{file}" controls>
        <track kind="captions" />
      </video>
    {:else}
      <img alt="{file}" src="{file}" class="h-auto w-full" />
    {/if}
  </div>
{/each}

কেন Jira Kanban Board-এর একটি ওপেন সোর্স অ্যাল্টারনেটিভ?

অনেক বেনিফিট আছে যা আপনাকে Jira Kanban Board-এর ওপেন সোর্স অ্যাল্টারনেটিভের দিকে নিয়ে যাবে hevaily পেইড সমাধান কেনার বদলে:

  • বেশি কস্ট সেভিংস: ওপেন সোর্স অ্যাল্টারনেটিভ ব্যবহার করার সবচেয়ে বড় সুবিধাগুলোর একটি হলো কস্ট সেভিংস। Jira-এর মত পেইড ক্যানবান বোর্ড সমাধানের বিপরীতে, SvelteKit, TailwindCSS, Firebase Storage, Upstash-এর সার্ভারলেস DB, এবং রেট লিমিটিং দিয়ে তৈরি একটি ওপেন সোর্স অ্যাল্টারনেটিভ কোনো লাইসেন্সিং ফি ছাড়াই ব্যবহার করা যায়।
  • অনলিমিটেড কাস্টোমাইজেবিলিটি: একটি ওপেন সোর্স অ্যাল্টারনেটিভের সাথে, আপনার কোডবেসের উপর পুরো কন্ট্রোল থাকে এবং আপনি আপনার নির্দিষ্ট প্রয়োজনের অনুযায়ী ক্যানবান বোর্ড কাস্টোমাইজ করতে পারেন। এই ফ্লেক্সিবিলিটি প্রায়শই পেইড সমাধানগুলোর সাথে সম্ভব নয় যাদের সীমিত কাস্টোমাইজেশন অপশন থাকে।
  • ইন্টিগ্রেশনে সহজতা: আপনি API-এর শক্তি ব্যবহার করে আপনার ক্যানবান বোর্ডকে প্রজেক্ট ম্যানেজমেন্ট সিস্টেম, ভার্সন কন্ট্রোল টুল, নোটিফিকেশন সার্ভিস, এবং আরও অনেক কিছুর সাথে সংযুক্ত করতে পারেন। এছাড়া, প্রজেক্টের ওপেন সোর্স প্রকৃতি ডেভেলপারদের এর ফাংশনালিটি এক্সটেন্ড করতে এবং তাদের নির্দিষ্ট প্রয়োজন অনুযায়ী প্লাগইন বা ইন্টিগ্রেশন তৈরি করতে দেয়।

উপসংহার

উপসংহারে, এই প্রজেক্টটি গ্র্যানুলার রেট লিমিটিং, CRUD ডেটা অপারেশন, Firebase Storage APIs ইমপ্লিমেন্ট করা ফাইল পাওয়া এবং আপলোড করা, এই সব Upstash-এর @upstash/redis লাইব্রেরি দিয়ে এজ-এ করার একটি মূল্যবান অভিজ্ঞতা প্রদান করেছে।

  1. HTML এবং CSS দিয়ে মেগা মেনু (ফুল-উইডথ ড্রপডাউন) তৈরি

  2. জাভা ৯-এ destroyForcibly() মেথডের গুরুত্ব

  3. জাভাস্ক্রিপ্ট array.entries() মেথড

  4. HTML DOM Figure অবজেক্ট