<पी> इस पोस्ट में, मैं इस बारे में बात करता हूं कि कैसे मैंने अपस्टैश, स्वेलटेकिट और फायरबेस स्टोरेज का उपयोग करके जिरा कानबन बोर्ड का एक ओपन-सोर्स विकल्प बनाया। <पी>
हम क्या उपयोग करेंगे
- SvelteKit (यूआई और एपीआई रूट)
- अपस्टैश (CRUD ऑपरेशंस)
- टेलविंड सीएसएस (स्टाइलिंग)
- फायरबेस स्टोरेज (एसेट [छवियां, पीडीएफ, आदि] स्टोरेज)
- Auth.js द्वारा SvelteKit प्रामाणिक
आपको क्या चाहिए
- डेटाबेस बनाने के लिए एक अपस्टैश खाता
- स्टोरेज कंटेनर बनाने के लिए एक फायरबेस खाता
- OAuth क्रेडेंशियल प्राप्त करने के लिए एक Google OAuth 2.0 सेटअप
अपस्टैश रेडिस की स्थापना
<पी> एक बार जब आप एक अपस्टैश खाता बना लेते हैं और लॉग इन हो जाते हैं तो आप रेडिस टैब पर जाएंगे और एक डेटाबेस बनाएंगे। <पी>
<पी>
<पी> अपना डेटाबेस बनाने के बाद, आप विवरण टैब पर जा रहे हैं। जब तक आपको अपना डेटाबेस कनेक्ट करें अनुभाग नहीं मिल जाता तब तक नीचे स्क्रॉल करें। सामग्री को कॉपी करें और इसे किसी सुरक्षित स्थान पर सहेजें। <पी>
<पी> इसके अलावा, तब तक नीचे स्क्रॉल करें जब तक आपको 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'
}
})
}
अपस्टैश रेडिस के माध्यम से सीआरयूडी संचालन जारी करें
<पी> इस अनुभाग में, हम गहराई से जानेंगे कि कानबन बोर्ड पर प्रत्येक अंक के लिए डेटा कैसे प्राप्त किया जाता है, अद्यतन किया जाता है और हटाया जाता है। हम अपस्टैश डीबी का निरंतर उपयोग करते हैं (@upstash/redis के माध्यम से)। ) डेटा लाने, प्रदर्शित करने और ताज़ा करने के लिए। getTask:समस्या डेटा फ़ंक्शन लाया जा रहा है
<पी> getTask फ़ंक्शन अपस्टैश के hget का उपयोग करता है id के माध्यम से प्रासंगिक मुद्दे एटीए के लिए अपस्टैश को एपीआई अनुरोध करने की कुंजी के रूप में, एक अद्वितीय id द्वारा पहचाना गया . यदि वह समस्या मौजूद नहीं है (या कोई त्रुटि है), तो फ़ंक्शन को { 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 प्रमाणित उपयोगकर्ता तक सीमित करने में सक्षम हूं। हम प्रमाणित उपयोगकर्ता के उपयोगकर्ता ईमेल और कार्य की आईडी के आधार पर इस दर सीमा को लागू करने में सक्षम हैं। जब भी अपलोड सफलतापूर्वक पूरा हो जाता है, हम अपस्टैश डीबी में कार्य को फ़ाइल यूआरएल के साथ अपडेट कर देते हैं। // 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,
});
}
फ़ायरबेस स्टोरेज के साथ फ़ाइल अपलोड और डाउनलोड को संभालना
<पी> इस अनुभाग में, हम गहराई से जानेंगे कि किसी समस्या की फ़ाइल अपलोड और डाउनलोड को SvelteKit के किनारे पर सुरक्षित और प्रमाणित तरीके से कैसे प्रबंधित किया जाता है। हम फ़ाइलें लाने और अपलोड करने के लिए फायरबेस (v9) स्टोरेज का लाभ उठाते हैं। ओह, लेकिन स्टोरेज के लिए Cloudflare R2 क्यों नहीं?
<पी> हालाँकि मैंने क्लाउडफ़ेयर R2 की मुफ़्त स्टोरेज योजना और इसके फ़ायदों के लिए बहुत सारी सामुदायिक वकालत देखी है, लेकिन एक चीज़ जिसने मुझे निराश किया वह थी सिस्टम को आज़माने से पहले अपना क्रेडिट कार्ड क्लाउडफ़ेयर के निपटान में डालने की आवश्यकता। इसने मुझे अन्य भंडारण समाधानों के बारे में सोचने पर मजबूर कर दिया, और मैं फायरबेस स्टोरेज तक पहुंचा, जो मुझे 5 जीबी मुफ्त स्टोरेज प्रदान करता है, और यदि मैं इससे अधिक करता हूं, तो मेरी मंजूरी के बिना मेरे क्रेडिट कार्ड से शुल्क लेने के बजाय मेरी सेवाएं बंद कर दी जाएंगी और जानें कि क्या हो रहा है। फ़ायरबेस स्टोरेज पर फ़ाइलें अपलोड करने के लिए SvelteKit Edge फ़ंक्शन
<पी> निम्नलिखित एज फ़ंक्शन में, हम किसी भी POST अनुरोध ईवेंट को देख रहे हैं और यदि उपयोगकर्ता प्रमाणित है, तो हमें taskID प्राप्त होता है और file इवेंट के फॉर्मडेटा से। ऐसा करने के बाद, हम आगे मूल्यांकन करते हैं कि यदि फ़ाइल का आकार 5 एमबी से कम है तो जारी रखना है या नहीं। एक बार सभी आवश्यकताओं का ध्यान रखने के बाद, हम एक अद्वितीय आईडी बनाते हैं, और फिर उस अद्वितीय फ़ोल्डर के लिए एक फायरबेस का संदर्भ बनाते हैं जहां फ़ाइल अपलोड की जाएगी। जैसे ही फ़ाइल फायरबेस पर अपलोड की जाती है, यह हमें एक यूआरएल लौटाता है जिसका उपयोग अपलोड की गई फ़ाइल तक पहुंचने के लिए किया जा सकता है। हम इस अद्वितीय 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 फ़ंक्शन
<पी> जैसा कि आपको याद होगा, हमने फ़ायरबेस द्वारा लौटाए गए अद्वितीय यूआरएल को अंक के files में जोड़ा था। कुंजी. हमें मूल फ़ाइल को पुनः प्राप्त करने के लिए SvelteKit के एज फ़ंक्शन के GET अनुरोध में छवि पैरामीटर के रूप में वह अद्वितीय 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}
लेकिन जीरा कंबन बोर्ड का एक खुला स्रोत विकल्प क्यों?
<पी> ऐसे कई लाभ हैं जो आपको भारी भुगतान वाले समाधान खरीदने के बजाय जिरा कानबन बोर्ड के ओपन सोर्स विकल्प के साथ जाने पर मजबूर करेंगे: - अधिक लागत बचत:ओपन सोर्स विकल्प का उपयोग करने का सबसे महत्वपूर्ण लाभ लागत बचत है। जीरा जैसे भुगतान किए गए कानबन बोर्ड समाधानों के विपरीत, स्वेलटेकिट, टेलविंडसीएसएस, फायरबेस स्टोरेज, अपस्टैश के सर्वरलेस डीबी और रेट लिमिटिंग के साथ निर्मित एक ओपन सोर्स विकल्प का उपयोग बिना किसी लाइसेंस शुल्क के किया जा सकता है।
- असीमित कस्टमाइज़ेबिलिटी:एक ओपन सोर्स विकल्प के साथ, आपके पास कोडबेस पर पूर्ण नियंत्रण होता है और आप अपनी विशिष्ट आवश्यकताओं के अनुसार कानबन बोर्ड को कस्टमाइज़ कर सकते हैं। यह लचीलापन अक्सर उन सशुल्क समाधानों के साथ संभव नहीं होता है जिनमें सीमित अनुकूलन विकल्प होते हैं।
- एकीकरण में आसानी:आप अपने कानबन बोर्ड को परियोजना प्रबंधन प्रणाली, संस्करण नियंत्रण उपकरण, अधिसूचना सेवाओं और बहुत कुछ से जोड़ने के लिए एपीआई की शक्ति का लाभ उठा सकते हैं। इसके अतिरिक्त, प्रोजेक्ट की ओपन सोर्स प्रकृति डेवलपर्स को इसकी कार्यक्षमता बढ़ाने और उनकी विशिष्ट आवश्यकताओं के अनुरूप प्लगइन या एकीकरण बनाने की अनुमति देती है।
निष्कर्ष
<पी> अंत में, इस परियोजना ने ग्रैन्युलर रेट लिमिटिंग, सीआरयूडी डेटा ऑपरेशंस को लागू करने, फ़ाइलों को प्राप्त करने और अपलोड करने के लिए फायरबेस स्टोरेज एपीआई को लागू करने में मूल्यवान अनुभव प्रदान किया है, यह सब अपस्टैश के @upstash/redis के साथ किनारे पर किया गया है। पुस्तकालय!