এই পোস্টে, আমি কীভাবে জিরা কানবান বোর্ডের একটি ওপেন-সোর্স বিকল্প আপস্ট্যাশ, স্বেল্টকিট এবং ফায়ারবেস স্টোরেজ ব্যবহার করে তৈরি করেছি তা নিয়ে কথা বলব৷

আমরা কি ব্যবহার করব
- SvelteKit (UI এবং API রুট)
- Upstash (CRUD অপারেশন)
- Tailwind CSS (স্টাইলিং)
- ফায়ারবেস স্টোরেজ (সম্পদ [ছবি, পিডিএফ, ইত্যাদি] স্টোরেজ)
- Auth.js দ্বারা SvelteKit প্রমাণ
আপনার যা প্রয়োজন
- ডাটাবেস তৈরি করার জন্য একটি Upstash অ্যাকাউন্ট
- একটি স্টোরেজ কন্টেইনার তৈরি করার জন্য একটি ফায়ারবেস অ্যাকাউন্ট
- OAuth শংসাপত্রগুলি পেতে একটি Google OAuth 2.0 সেটআপ
Upstash Redis সেট আপ করা হচ্ছে
একবার আপনি একটি Upstash অ্যাকাউন্ট তৈরি করেছেন এবং লগ ইন করলে আপনি Redis ট্যাবে যান এবং একটি ডাটাবেস তৈরি করতে যাচ্ছেন৷


আপনি আপনার ডাটাবেস তৈরি করার পরে, আপনি তারপরে বিশদ ট্যাবে যাচ্ছেন। আপনি আপনার ডাটাবেস সংযোগ বিভাগটি খুঁজে না পাওয়া পর্যন্ত নিচে স্ক্রোল করুন। বিষয়বস্তু অনুলিপি করুন এবং এটি নিরাপদ কোথাও সংরক্ষণ করুন৷
৷

এছাড়াও, আপনি REST API বিভাগটি খুঁজে না পাওয়া পর্যন্ত নিচে স্ক্রোল করুন এবং .env বোতামটি নির্বাচন করুন। বিষয়বস্তু অনুলিপি করুন এবং এটি নিরাপদ কোথাও সংরক্ষণ করুন৷
৷

প্রকল্প সেট আপ করা হচ্ছে
সেট আপ করতে, শুধু অ্যাপ রেপো ক্লোন করুন এবং এতে যা আছে তা শিখতে এই টিউটোরিয়ালটি অনুসরণ করুন। প্রকল্পটি ফর্ক করতে, চালান:
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
# Obtained from Google OAuth 2.0 setup
# https://support.google.com/cloud/answer/6158849?hl=en
GOOGLE_ID="..."
GOOGLE_SECRET="..."
# SvelteKit Auth
AUTH_SECRET="..." # A random 32 char string
AUTH_TRUST_HOST=true
# Obtained from Upstash as from the steps done above
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
// with the firebase config obtained from your firebase project
// Read more about firebase config
// 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, এবং ফাইল আপলোড হ্যান্ডলারের সাথে সম্পর্কিত ফাইলগুলির সাথে তাদের উল্লেখ করা হয়েছে৷

ব্যবহারকারী প্রমাণীকরণ দ্বারা SvelteKit এর এজ ফাংশন রক্ষা করা
Auth.js এ টিমের একটি দুর্দান্ত কাজ৷ SvelteKit এর সাথে Auth কে একটি বিরামহীন অপারেশন করেছে। প্রকল্পটি বাস্তবায়ন করে:
Google OAuth 2.0 ব্যবহার করে সমস্ত পৃষ্ঠায় অনুমোদন
SvelteKit-এর সার্ভার হুক ব্যবহার করে, আমরা সমস্ত আগত অনুরোধে (যেকোন পৃষ্ঠায়) প্রমাণীকরণ প্রয়োগ করি:
// 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";
// Read more on
// 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-এর সার্ভার স্থানীয় ব্যবহার করে এজ ফাংশন(গুলি) এর অনুমোদন
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'
// Get user session if available in event locals
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 user is not authenticated throw a 403
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'
}
})
} ইস্যু(গুলি) আপস্ট্যাশ রেডিসের মাধ্যমে CRUD অপারেশনগুলি
এই বিভাগে, আমরা কানবান বোর্ডে প্রতিটি সমস্যার জন্য ডেটা আনা, আপডেট করা এবং মুছে ফেলার বিষয়ে গভীরভাবে আলোচনা করব। আমরা Upstash DB(@upstash/redis এর মাধ্যমে নিয়মিত ব্যবহার করি ) ডেটা আনয়ন, প্রদর্শন এবং রিফ্রেশ করতে।
getTask:সমস্যা ডেটা ফাংশন আনা হচ্ছে
getTask ফাংশন Upstash-এর hget ব্যবহার করে id এর মাধ্যমে একটি অনন্য id দ্বারা চিহ্নিত প্রাসঙ্গিক সমস্যার জন্য Upstash-এর কাছে API অনুরোধ করার চাবিকাঠি হিসাবে . যদি সেই সমস্যাটি উপস্থিত না থাকে (অথবা একটি ত্রুটি থাকে), ফাংশনটি একটি { code: 0 } সহ একটি বস্তু ফেরত দিতে সেট করা হয় যাতে ব্যবহারকারীকে স্বয়ংক্রিয়ভাবে SvelteKit-এর গতিশীল রুটে 404 (সমস্যা পাওয়া যায়নি) এ রিডাইরেক্ট করা যায়।
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 নামে একটি রেট লিমিটার লাইব্রেরি .
// Reference Function to ratelimiting
// 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"),
}),
}; রেট লিমিটিং ব্যবহার করে, আমি নিম্নলিখিতগুলি অর্জন করতে সক্ষম হয়েছি:
ক. ব্যবহারকারী প্রতি মিনিটে ইস্যু তৈরির সীমা সংখ্যা
রেট লিমিটিং ব্যবহার করে, আমি প্রতি মিনিটে প্রমাণীকৃত ব্যবহারকারী প্রতি পাঁচটি ইস্যু তৈরি সীমিত করতে সক্ষম। আমরা প্রমাণীকৃত ব্যবহারকারীর ব্যবহারকারীর ইমেলের উপর ভিত্তি করে এই হারের সীমা কার্যকর করতে সক্ষম।
// 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) {
// Look at the user email of authenticated user at edge
// Rate limit 5 issues creation per minute
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,
});
} বি. ব্যবহারকারী প্রতি ইস্যু প্রতি মিনিটে ফাইল আপলোডের সীমা সংখ্যা
রেট লিমিটিং ব্যবহার করে, আমি প্রতি মিনিটে প্রতি টাস্ক প্রতি প্রমাণীকৃত ব্যবহারকারী 2 পর্যন্ত ফাইল আপলোড সীমাবদ্ধ করতে সক্ষম। আমরা প্রমাণীকৃত ব্যবহারকারীর ব্যবহারকারীর ইমেল এবং টাস্কের আইডির উপর ভিত্তি করে এই হারের সীমা কার্যকর করতে সক্ষম। যখনই আপলোড সফলভাবে সম্পন্ন হয়, আমরা 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) {
// Validate User, Task ID and if a file is uploaded
// Look at the user email of authenticated user and task's ID at edge
// Rate limit 2 uploads per minute
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
// Continue reading the blog to see how
// file uploads are being taken care of
}
return new Response(undefined, {
status: 403,
});
} Firebase স্টোরেজ দিয়ে ফাইল আপলোড এবং ডাউনলোড পরিচালনা করা
এই বিভাগে, আমরা SvelteKit-এর প্রান্তে কীভাবে একটি সমস্যার ফাইল আপলোড এবং ডাউনলোডগুলি নিরাপদ এবং প্রমাণীকৃত পদ্ধতিতে পরিচালনা করা হয় সে সম্পর্কে গভীরভাবে আলোচনা করব। আমরা ফায়ারবেস (v9) স্টোরেজের সুবিধা নিয়ে থাকি এবং ফাইল আপলোড করি।
ওহ, তবে স্টোরেজের জন্য ক্লাউডফ্লেয়ার R2 কেন নয়?
যদিও আমি ক্লাউডফ্লেয়ার R2-এর বিনামূল্যের স্টোরেজ প্ল্যান এবং এর সুবিধার জন্য অনেক কমিউনিটি অ্যাডভোকেসি দেখেছি, একটি জিনিস যা আমাকে বাদ দিয়েছিল তা হল সিস্টেমটি চেষ্টা করার আগে আমার ক্রেডিট কার্ডটি ক্লাউডফ্লেয়ারের নিষ্পত্তিতে রাখার প্রয়োজন। এটি আমাকে অন্যান্য স্টোরেজ সমাধান সম্পর্কে চিন্তা করতে বাধ্য করেছে এবং আমি Firebase স্টোরেজ পর্যন্ত পৌঁছেছি যা আমাকে 5 GB বিনামূল্যের সঞ্চয়স্থান অফার করে এবং যদি আমি এটি অতিক্রম করি, আমার অনুমোদন ছাড়াই আমার ক্রেডিট কার্ড চার্জ করার পরিবর্তে আমার পরিষেবাগুলি বন্ধ করে দেওয়া হবে এবং কীভাবে ঘটছে তা জানুন।
Firebase স্টোরেজে ফাইল আপলোড করার জন্য SvelteKit Edge ফাংশন
নিম্নলিখিত এজ ফাংশনে, আমরা যেকোন POST অনুরোধ ইভেন্ট দেখছি এবং যদি ব্যবহারকারী প্রমাণীকৃত হয়, আমরা taskID পাই এবং file ইভেন্টের ফর্মডেটা থেকে। এটি করার সাথে সাথে, ফাইলের আকার 5 MB এর নিচে হলে আমরা চালিয়ে যেতে হবে কিনা তা আরও মূল্যায়ন করি। সমস্ত পূর্বশর্তের যত্ন নেওয়া হয়ে গেলে, আমরা একটি অনন্য আইডি তৈরি করি এবং তারপরে ফাইলটি আপলোড করা অনন্য ফোল্ডারে একটি ফায়ারবেসের রেফারেন্স তৈরি করি। ফাইলটি ফায়ারবেসে আপলোড হওয়ার সাথে সাথে এটি আমাদেরকে একটি 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,
});
} Firebase স্টোরেজ থেকে ফাইলের সর্বজনীন URL ডাউনলোড করতে SvelteKit Edge ফাংশন
আপনি যেমন মনে করবেন, আমরা সমস্যার files এ Firebase দ্বারা প্রত্যাবর্তিত অনন্য URL যুক্ত করেছি চাবি আমরা আসল ফাইলটি পুনরুদ্ধার করার জন্য SvelteKit-এর এজ ফাংশনের GET অনুরোধে চিত্রের প্যারামিটার হিসাবে সেই অনন্য URLটি পেয়েছি। আসল মিডিয়ার সর্বজনীন URL পেতে আমরা ফায়ারবেসের লাইব্রেরি থেকে getDownloadURL ফাংশন ব্যবহার করি।
// 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",
},
});
} আপনি ইতিমধ্যেই ভাবছেন, এমন একাধিক মিডিয়া থাকতে পারে যা আপলোড করা যেতে পারে, তাই একটি ছবি বনাম ভিডিওর তুচ্ছ ঘটনাটি পরিচালনা করার জন্য, আমি সামনের প্রান্তে অন্যথায় নিম্নলিখিতটি যুক্ত করেছি:
<!-- 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} কিন্তু জিরা কানবান বোর্ডের একটি ওপেন সোর্স বিকল্প কেন?
অনেক সুবিধা আছে যা আপনাকে জিরা কানবান বোর্ডের ওপেন সোর্স বিকল্পের সাথে বেশি অর্থ প্রদানের সমাধান কেনার পরিবর্তে যেতে সাহায্য করবে:
- অনেক খরচ সঞ্চয়:একটি ওপেন সোর্স বিকল্প ব্যবহার করার সবচেয়ে উল্লেখযোগ্য সুবিধাগুলির মধ্যে একটি হল খরচ সঞ্চয়। জিরার মত পেইড কানবান বোর্ড সলিউশনের বিপরীতে, SvelteKit, TailwindCSS, Firebase স্টোরেজ, Upstash এর সার্ভারলেস DB, এবং রেট লিমিটিং সহ নির্মিত একটি ওপেন সোর্স বিকল্প কোনো লাইসেন্সিং ফি ছাড়াই ব্যবহার করা যেতে পারে।
- আনলিমিটেড কাস্টমাইজেবিলিটি:একটি ওপেন সোর্স বিকল্পের সাথে, কোডবেসের উপর আপনার সম্পূর্ণ নিয়ন্ত্রণ রয়েছে এবং আপনার নির্দিষ্ট চাহিদা অনুযায়ী কানবান বোর্ড কাস্টমাইজ করতে পারেন। সীমিত কাস্টমাইজেশন বিকল্প আছে এমন অর্থপ্রদানের সমাধানগুলির সাথে এই নমনীয়তা প্রায়শই সম্ভব হয় না৷ ৷
- ইজ ইন ইন্টিগ্রেশন:আপনি প্রজেক্ট ম্যানেজমেন্ট সিস্টেম, ভার্সন কন্ট্রোল টুল, নোটিফিকেশন সার্ভিস এবং আরও অনেক কিছুর সাথে আপনার কানবান বোর্ডের সাথে সংযোগ করতে API-এর শক্তি ব্যবহার করতে পারেন। উপরন্তু, প্রকল্পের ওপেন সোর্স প্রকৃতি ডেভেলপারদের এর কার্যকারিতা প্রসারিত করতে এবং তাদের নির্দিষ্ট প্রয়োজনীয়তা অনুসারে প্লাগইন বা ইন্টিগ্রেশন তৈরি করতে দেয়।
উপসংহার
উপসংহারে, এই প্রকল্পটি গ্রানুলার রেট লিমিটিং, CRUD ডেটা অপারেশন, ফাইলগুলি পেতে এবং আপলোড করার জন্য Firebase স্টোরেজ এপিআই প্রয়োগ করার ক্ষেত্রে মূল্যবান অভিজ্ঞতা প্রদান করেছে, এটি সবই Upstash-এর @upstash/redis এর মাধ্যমে করা হয়েছে। লাইব্রেরি!