mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-05 00:32:03 +00:00
migration to not require badges be linked to course, fixed in flow, testing out real plebdevs badges
This commit is contained in:
parent
85bce5544d
commit
5e579614d7
@ -0,0 +1,8 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Badge" DROP CONSTRAINT "Badge_courseId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Badge" ALTER COLUMN "courseId" DROP NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Badge" ADD CONSTRAINT "Badge_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -1,3 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
@ -1,14 +1,14 @@
|
||||
// datasource db {
|
||||
// provider = "postgresql"
|
||||
// url = env("DATABASE_URL")
|
||||
// }
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("POSTGRES_PRISMA_URL")
|
||||
directUrl = env("POSTGRES_URL_NON_POOLING")
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// datasource db {
|
||||
// provider = "postgresql"
|
||||
// url = env("POSTGRES_PRISMA_URL")
|
||||
// directUrl = env("POSTGRES_URL_NON_POOLING")
|
||||
// }
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
@ -254,8 +254,8 @@ model Badge {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
noteId String @unique
|
||||
courseId String @unique // One badge per course
|
||||
course Course @relation(fields: [courseId], references: [id])
|
||||
courseId String? @unique // Optional relation to course
|
||||
course Course? @relation(fields: [courseId], references: [id])
|
||||
userBadges UserBadge[] // Many users can have this badge
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
@ -133,12 +133,7 @@ const HeroBanner = () => {
|
||||
className="border-2"
|
||||
size={isMobile ? null : "large"}
|
||||
outlined
|
||||
onClick={() => signIn('anonymous', {
|
||||
callbackUrl: '/profile',
|
||||
redirect: true,
|
||||
pubkey: null,
|
||||
privkey: null
|
||||
})}
|
||||
onClick={() => router.push('/course/naddr1qvzqqqr4xspzpueu32tp0jc47uzlcuxdgcw06m40ytu7ynpna2adnqty3e0vda6pqy88wumn8ghj7mn0wvhxcmmv9uq32amnwvaz7tmjv4kxz7fwv3sk6atn9e5k7tcpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uq3wamnwvaz7tmjv4kxz7fwdehhxarj9e3xzmny9uq36amnwvaz7tmjv4kxz7fwd46hg6tw09mkzmrvv46zucm0d5hsz9mhwden5te0wfjkccte9ec8y6tdv9kzumn9wshszynhwden5te0dehhxarjxgcjucm0d5hszynhwden5te0dehhxarjw4jjucm0d5hsz9nhwden5te0wp6hyurvv4ex2mrp0yhxxmmd9uq3wamnwvaz7tmjv4kxz7fwv3jhvueww3hk7mrn9uqzge34xvuxvdtrx5knzcfhxgkngwpsxsknsetzxyknxe3sx43k2cfkxsurwdq68epwa?active=0')}
|
||||
/>
|
||||
<GenericButton
|
||||
label="Level Up"
|
||||
|
@ -21,7 +21,7 @@ const UserBadges = ({ visible, onHide }) => {
|
||||
// Fetch badge definitions (kind 30009)
|
||||
const badgeDefinitions = await ndk.fetchEvents({
|
||||
// todo: add the plebdevs hardcoded badge ids (probably in config?)
|
||||
ids: ["97777aaccfb409ab973d30fc3a27de5ca64080c13a0bca6c2c261105ae545118"]
|
||||
ids: ["4054a68f028edf38cd1d71cc4693d4ff5c9c54b0b44532361fe6abb29530cbf6", "5d38fea9a3c1fb4c55c9635c3132d34608c91de640f772438faa1942677087a8", "3ba20936d66523adb6d71793649bc77f3cea34f50c21ec7bb2c041f936022214", "41edee5af6d4e833d11f9411c2c27cc48c14d2a3c7966ae7648568e825eda1ed"]
|
||||
});
|
||||
|
||||
console.log("Badge Definitions: ", badgeDefinitions);
|
||||
@ -30,7 +30,7 @@ const UserBadges = ({ visible, onHide }) => {
|
||||
const badgeAwards = await ndk.fetchEvents({
|
||||
kinds: [8],
|
||||
// todo: add the plebdevs author pubkey
|
||||
authors: ["62bad2c804210b9ccd8b3d6b49da7333185bae17c12b6d7a8ed5865642e82b1e"],
|
||||
authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"],
|
||||
"#p": [session.user.pubkey]
|
||||
});
|
||||
|
||||
|
@ -22,8 +22,8 @@ const allTasks = [
|
||||
status: 'PlebDevs Starter',
|
||||
completed: false,
|
||||
tier: 'Plebdev',
|
||||
courseId: "f538f5c5-1a72-4804-8eb1-3f05cea64874",
|
||||
// courseId: "5664e78f-c618-410d-a7cc-f3393b021fdf",
|
||||
// courseId: "f538f5c5-1a72-4804-8eb1-3f05cea64874",
|
||||
courseId: "5664e78f-c618-410d-a7cc-f3393b021fdf",
|
||||
subTasks: [
|
||||
{ status: 'Complete the course', completed: false },
|
||||
]
|
||||
|
@ -12,7 +12,7 @@ export const useBadge = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
if (!session?.user || isProcessing || !completedCourses) return;
|
||||
if (!session?.user || isProcessing) return;
|
||||
|
||||
const checkForBadgeEligibility = async () => {
|
||||
setIsProcessing(true);
|
||||
@ -22,6 +22,29 @@ export const useBadge = () => {
|
||||
const { userBadges } = session.user;
|
||||
let badgesAwarded = false;
|
||||
|
||||
// Check for GitHub connection badge
|
||||
if (session?.account?.provider === 'github') {
|
||||
const hasPlebBadge = userBadges?.some(
|
||||
userBadge => userBadge.badge?.id === '3664e78f-b618-420d-a7cc-f3393b0211df'
|
||||
);
|
||||
|
||||
if (!hasPlebBadge) {
|
||||
try {
|
||||
const response = await axios.post('/api/badges/issue', {
|
||||
badgeId: '3664e78f-b618-420d-a7cc-f3393b0211df',
|
||||
userId: session.user.id,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
badgesAwarded = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error issuing Pleb badge:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for course-related badges
|
||||
const eligibleCourses = completedCourses?.filter(userCourse => {
|
||||
const isCompleted = userCourse.completed;
|
||||
const hasNoBadge = !userBadges?.some(
|
||||
@ -35,7 +58,7 @@ export const useBadge = () => {
|
||||
for (const course of eligibleCourses || []) {
|
||||
try {
|
||||
const response = await axios.post('/api/badges/issue', {
|
||||
courseId: course.courseId,
|
||||
courseId: course?.courseId,
|
||||
userId: session.user.id,
|
||||
});
|
||||
|
||||
|
@ -26,45 +26,51 @@ export default async function handler(req, res) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const { courseId, userId } = req.body;
|
||||
const { courseId, badgeId, userId } = req.body;
|
||||
|
||||
// Verify course completion and get badge details
|
||||
const userCourse = await prisma.userCourse.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
courseId,
|
||||
completed: true,
|
||||
},
|
||||
include: {
|
||||
course: {
|
||||
include: {
|
||||
badge: true,
|
||||
},
|
||||
let badge;
|
||||
if (courseId && courseId !== null && courseId !== undefined) {
|
||||
// Existing course badge logic
|
||||
const userCourse = await prisma.userCourse.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
courseId,
|
||||
completed: true,
|
||||
},
|
||||
user: true, // Include user to get pubkey
|
||||
},
|
||||
});
|
||||
include: {
|
||||
course: {
|
||||
include: {
|
||||
badge: true,
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!userCourse) {
|
||||
return res.status(400).json({ error: 'Course not completed' });
|
||||
}
|
||||
if (!userCourse) {
|
||||
return res.status(400).json({ error: 'Course not completed' });
|
||||
}
|
||||
|
||||
if (!userCourse.course.badge) {
|
||||
return res.status(400).json({ error: 'No badge defined for this course' });
|
||||
}
|
||||
badge = userCourse.course.badge;
|
||||
} else if (badgeId) {
|
||||
// Direct badge lookup for non-course badges
|
||||
badge = await prisma.badge.findUnique({
|
||||
where: { id: badgeId },
|
||||
include: { userBadges: true },
|
||||
});
|
||||
|
||||
let noteId = userCourse.course.badge.noteId;
|
||||
|
||||
if (noteId && noteId.startsWith("naddr")) {
|
||||
const naddr = nip19.decode(noteId);
|
||||
noteId = `${naddr.data.kind}:${naddr.data.pubkey}:${naddr.data.identifier}`;
|
||||
if (!badge) {
|
||||
return res.status(400).json({ error: 'Badge not found' });
|
||||
}
|
||||
} else {
|
||||
return res.status(400).json({ error: 'Either courseId or badgeId is required' });
|
||||
}
|
||||
|
||||
// Check if badge already exists
|
||||
const existingBadge = await prisma.userBadge.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
badgeId: userCourse.course.badge.id,
|
||||
badgeId: badge.id,
|
||||
},
|
||||
});
|
||||
|
||||
@ -72,6 +78,18 @@ export default async function handler(req, res) {
|
||||
return res.status(400).json({ error: 'Badge already awarded' });
|
||||
}
|
||||
|
||||
// Get user for pubkey
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
let noteId = badge.noteId;
|
||||
|
||||
if (noteId && noteId.startsWith("naddr")) {
|
||||
const naddr = nip19.decode(noteId);
|
||||
noteId = `${naddr.data.kind}:${naddr.data.pubkey}:${naddr.data.identifier}`;
|
||||
}
|
||||
|
||||
// Get the signing key from environment and convert to bytes
|
||||
const signingKey = process.env.BADGE_SIGNING_KEY;
|
||||
if (!signingKey) {
|
||||
@ -84,21 +102,15 @@ export default async function handler(req, res) {
|
||||
kind: BADGE_AWARD_KIND,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
['p', userCourse.user.pubkey],
|
||||
['p', user.pubkey],
|
||||
['a', noteId],
|
||||
['d', `course-completion-${userCourse.course.id}`],
|
||||
['d', `plebdevs-badge-award-${session.user.id}`],
|
||||
],
|
||||
content: JSON.stringify({
|
||||
name: userCourse.course.badge.name,
|
||||
description: `Completed ${userCourse.course.id}`,
|
||||
image: userCourse.course.badge.noteId,
|
||||
course: courseId,
|
||||
awardedAt: new Date().toISOString(),
|
||||
})
|
||||
content: ""
|
||||
};
|
||||
|
||||
// Add validation for required fields
|
||||
if (!userCourse.user.pubkey || !noteId) {
|
||||
if (!user.pubkey || !noteId) {
|
||||
return res.status(400).json({
|
||||
error: 'Missing required fields',
|
||||
message: 'Pubkey and noteId are required'
|
||||
@ -108,6 +120,8 @@ export default async function handler(req, res) {
|
||||
// Finalize (sign) the event
|
||||
const signedEvent = finalizeEvent(eventTemplate, signingKeyBytes);
|
||||
|
||||
console.log("Signed Event: ", signedEvent);
|
||||
|
||||
// Verify the event
|
||||
const isValid = verifyEvent(signedEvent);
|
||||
if (!isValid) {
|
||||
@ -136,7 +150,7 @@ export default async function handler(req, res) {
|
||||
const userBadge = await prisma.userBadge.create({
|
||||
data: {
|
||||
userId,
|
||||
badgeId: userCourse.course.badge.id,
|
||||
badgeId: badge.id,
|
||||
awardedAt: new Date(),
|
||||
},
|
||||
include: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user