mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-19 10:51:20 +00:00
Added badges to schema, added badges models, same for userbadges, added basic hardcoded ui for badges
This commit is contained in:
parent
d5a05da1f7
commit
12defee451
@ -3,7 +3,7 @@ const removeImports = require("next-remove-imports")();
|
|||||||
module.exports = removeImports({
|
module.exports = removeImports({
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
images: {
|
images: {
|
||||||
domains: ['localhost', 'secure.gravatar.com', 'plebdevs-three.vercel.app', 'plebdevs.com'],
|
domains: ['localhost', 'secure.gravatar.com', 'plebdevs-three.vercel.app', 'plebdevs.com', 'plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com'],
|
||||||
},
|
},
|
||||||
webpack(config, options) {
|
webpack(config, options) {
|
||||||
return config;
|
return config;
|
||||||
|
39
prisma/migrations/20241210224336_add_badges/migration.sql
Normal file
39
prisma/migrations/20241210224336_add_badges/migration.sql
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Badge" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"noteId" TEXT NOT NULL,
|
||||||
|
"courseId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Badge_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserBadge" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"badgeId" TEXT NOT NULL,
|
||||||
|
"awardedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "UserBadge_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Badge_noteId_key" ON "Badge"("noteId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Badge_courseId_key" ON "Badge"("courseId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "UserBadge_userId_badgeId_key" ON "UserBadge"("userId", "badgeId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Badge" ADD CONSTRAINT "Badge_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserBadge" ADD CONSTRAINT "UserBadge_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserBadge" ADD CONSTRAINT "UserBadge_badgeId_fkey" FOREIGN KEY ("badgeId") REFERENCES "Badge"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
@ -1,14 +1,14 @@
|
|||||||
// datasource db {
|
|
||||||
// provider = "postgresql"
|
|
||||||
// url = env("DATABASE_URL")
|
|
||||||
// }
|
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "postgresql"
|
provider = "postgresql"
|
||||||
url = env("POSTGRES_PRISMA_URL")
|
url = env("DATABASE_URL")
|
||||||
directUrl = env("POSTGRES_URL_NON_POOLING")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// datasource db {
|
||||||
|
// provider = "postgresql"
|
||||||
|
// url = env("POSTGRES_PRISMA_URL")
|
||||||
|
// directUrl = env("POSTGRES_URL_NON_POOLING")
|
||||||
|
// }
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
}
|
}
|
||||||
@ -38,6 +38,7 @@ model User {
|
|||||||
userCourses UserCourse[]
|
userCourses UserCourse[]
|
||||||
nip05 Nip05?
|
nip05 Nip05?
|
||||||
lightningAddress LightningAddress?
|
lightningAddress LightningAddress?
|
||||||
|
userBadges UserBadge[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Session {
|
model Session {
|
||||||
@ -132,6 +133,7 @@ model Course {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
userCourses UserCourse[]
|
userCourses UserCourse[]
|
||||||
|
badge Badge?
|
||||||
}
|
}
|
||||||
|
|
||||||
model CourseDraft {
|
model CourseDraft {
|
||||||
@ -247,3 +249,25 @@ model LightningAddress {
|
|||||||
lndHost String
|
lndHost String
|
||||||
lndPort String @default("8080")
|
lndPort String @default("8080")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
userBadges UserBadge[] // Many users can have this badge
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model UserBadge {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
badgeId String
|
||||||
|
badge Badge @relation(fields: [badgeId], references: [id])
|
||||||
|
awardedAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@unique([userId, badgeId]) // Each user can only have one of each badge
|
||||||
|
}
|
||||||
|
106
src/components/profile/UserBadges.js
Normal file
106
src/components/profile/UserBadges.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Dialog } from 'primereact/dialog';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
const UserBadges = ({ visible, onHide }) => {
|
||||||
|
// Hardcoded badges for now - later we'll fetch from nostr
|
||||||
|
const badges = [
|
||||||
|
{
|
||||||
|
name: "Pleb",
|
||||||
|
description: "You are signed up and ready to start your Dev Journey, onwards!",
|
||||||
|
image: "https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/images/badges/pleb/lg.png",
|
||||||
|
thumbnail: "https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/images/badges/pleb/sm.png",
|
||||||
|
awardedOn: "2024-03-15",
|
||||||
|
nostrId: "naddr1qq98getnw3e8getnw3eqzqqzyp3t45kgqsssh8xd3v7kkjw6wve3skawzlqjkmt63m2cv4jzaq43uqcyqqq82wgcvg0zv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Plebdev",
|
||||||
|
description: "You have completed the PlebDevs Starter and taken the first important step on your Dev Journey, congrats!",
|
||||||
|
image: "https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/images/badges/plebdev/1012.png",
|
||||||
|
thumbnail: "https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/images/badges/plebdev/256.png",
|
||||||
|
awardedOn: "2024-03-15",
|
||||||
|
nostrId: "naddr1qq98getnw3e8getnw3eqzqqzyp3t45kgqsssh8xd3v7kkjw6wve3skawzlqjkmt63m2cv4jzaq43uqcyqqq82wgcvg0zv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Frontend Dev",
|
||||||
|
description: "You have completed the Frontend Course and proven your proficiency at writing web apps and deploying Web Apps.",
|
||||||
|
image: "https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/images/badges/frontend/lg.png",
|
||||||
|
thumbnail: "https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/images/badges/frontend/sm.png",
|
||||||
|
awardedOn: "2024-03-15",
|
||||||
|
nostrId: "naddr1qq98getnw3e8getnw3eqzqqzyp3t45kgqsssh8xd3v7kkjw6wve3skawzlqjkmt63m2cv4jzaq43uqcyqqq82wgcvg0zv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Backend Dev",
|
||||||
|
description: "You have completed the Backend Course and demonstrated the ability to build and deploy Servers, API's, and Databases for Application Development.",
|
||||||
|
image: "https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/images/badges/backend/lg.png",
|
||||||
|
thumbnail: "https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/images/badges/backend/sm.png",
|
||||||
|
awardedOn: "2024-03-15",
|
||||||
|
nostrId: "naddr1qq98getnw3e8getnw3eqzqqzyp3t45kgqsssh8xd3v7kkjw6wve3skawzlqjkmt63m2cv4jzaq43uqcyqqq82wgcvg0zv"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
return new Date(dateString).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
visible={visible}
|
||||||
|
onHide={onHide}
|
||||||
|
header={
|
||||||
|
<div className="text-2xl font-bold text-white">
|
||||||
|
Your Badges Collection
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
className="w-[90vw] md:w-[70vw] lg:w-[50vw]"
|
||||||
|
contentClassName="bg-gray-900"
|
||||||
|
headerClassName="bg-gray-900 border-b border-gray-700"
|
||||||
|
>
|
||||||
|
<div className="p-6 bg-gray-900">
|
||||||
|
<p className="text-gray-400 mb-6">Showcase your achievements and progress through your dev journey</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{badges.map((badge, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="bg-gray-800 rounded-xl p-6 flex flex-col items-center transform transition-all duration-200 hover:scale-[1.02] hover:shadow-lg"
|
||||||
|
>
|
||||||
|
<div className="relative w-32 h-32 mb-4">
|
||||||
|
<Image
|
||||||
|
src={badge.thumbnail}
|
||||||
|
alt={badge.name}
|
||||||
|
layout="fill"
|
||||||
|
objectFit="contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-white font-semibold text-xl mb-2">{badge.name}</h3>
|
||||||
|
<p className="text-gray-400 text-center text-sm">{badge.description}</p>
|
||||||
|
|
||||||
|
<div className="mt-4 flex flex-col items-center gap-2 w-full">
|
||||||
|
<div className="bg-blue-500/10 text-blue-400 px-3 py-1 rounded-full text-sm">
|
||||||
|
Earned on {formatDate(badge.awardedOn)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={`https://nostrudel.ninja/#/badges/${badge.nostrId}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-purple-400 hover:text-purple-300 text-sm flex items-center gap-1 transition-colors"
|
||||||
|
>
|
||||||
|
<i className="pi pi-external-link" />
|
||||||
|
View on Nostr
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserBadges;
|
@ -4,6 +4,7 @@ import { Accordion, AccordionTab } from 'primereact/accordion';
|
|||||||
import { useSession, signIn, getSession } from 'next-auth/react';
|
import { useSession, signIn, getSession } from 'next-auth/react';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import GenericButton from '@/components/buttons/GenericButton';
|
import GenericButton from '@/components/buttons/GenericButton';
|
||||||
|
import UserBadges from '@/components/profile/UserBadges';
|
||||||
|
|
||||||
const allTasks = [
|
const allTasks = [
|
||||||
{
|
{
|
||||||
@ -12,14 +13,13 @@ const allTasks = [
|
|||||||
tier: 'Pleb',
|
tier: 'Pleb',
|
||||||
courseId: null,
|
courseId: null,
|
||||||
subTasks: [
|
subTasks: [
|
||||||
{ status: 'Create First GitHub Repo', completed: false },
|
{ status: 'Create Your First GitHub Repo', completed: false },
|
||||||
{ status: 'Push Commit', completed: false }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: 'PlebDevs Starter',
|
status: 'PlebDevs Starter',
|
||||||
completed: false,
|
completed: false,
|
||||||
tier: 'New Dev',
|
tier: 'Plebdev',
|
||||||
// courseId: "f538f5c5-1a72-4804-8eb1-3f05cea64874",
|
// courseId: "f538f5c5-1a72-4804-8eb1-3f05cea64874",
|
||||||
courseId: "f6daa88a-53d6-4901-8dbd-d2203a05b7ab",
|
courseId: "f6daa88a-53d6-4901-8dbd-d2203a05b7ab",
|
||||||
subTasks: [
|
subTasks: [
|
||||||
@ -29,7 +29,7 @@ const allTasks = [
|
|||||||
{
|
{
|
||||||
status: 'Frontend Course',
|
status: 'Frontend Course',
|
||||||
completed: false,
|
completed: false,
|
||||||
tier: 'Junior Dev',
|
tier: 'Frontend Dev',
|
||||||
courseId: 'f73c37f4-df2e-4f7d-a838-dce568c76136',
|
courseId: 'f73c37f4-df2e-4f7d-a838-dce568c76136',
|
||||||
subTasks: [
|
subTasks: [
|
||||||
{ status: 'Complete the course', completed: false },
|
{ status: 'Complete the course', completed: false },
|
||||||
@ -39,7 +39,7 @@ const allTasks = [
|
|||||||
{
|
{
|
||||||
status: 'Backend Course',
|
status: 'Backend Course',
|
||||||
completed: false,
|
completed: false,
|
||||||
tier: 'Plebdev',
|
tier: 'Backend Dev',
|
||||||
courseId: 'f6825391-831c-44da-904a-9ac3d149b7be',
|
courseId: 'f6825391-831c-44da-904a-9ac3d149b7be',
|
||||||
subTasks: [
|
subTasks: [
|
||||||
{ status: 'Complete the course', completed: false },
|
{ status: 'Complete the course', completed: false },
|
||||||
@ -54,6 +54,7 @@ const UserProgress = () => {
|
|||||||
const [expandedItems, setExpandedItems] = useState({});
|
const [expandedItems, setExpandedItems] = useState({});
|
||||||
const [completedCourses, setCompletedCourses] = useState([]);
|
const [completedCourses, setCompletedCourses] = useState([]);
|
||||||
const [tasks, setTasks] = useState([]);
|
const [tasks, setTasks] = useState([]);
|
||||||
|
const [showBadges, setShowBadges] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: session, update } = useSession();
|
const { data: session, update } = useSession();
|
||||||
@ -122,11 +123,11 @@ const UserProgress = () => {
|
|||||||
let tier = null;
|
let tier = null;
|
||||||
|
|
||||||
if (completedCourseIds.includes("f6825391-831c-44da-904a-9ac3d149b7be")) {
|
if (completedCourseIds.includes("f6825391-831c-44da-904a-9ac3d149b7be")) {
|
||||||
tier = 'Plebdev';
|
tier = 'Backend Dev';
|
||||||
} else if (completedCourseIds.includes("f73c37f4-df2e-4f7d-a838-dce568c76136")) {
|
} else if (completedCourseIds.includes("f73c37f4-df2e-4f7d-a838-dce568c76136")) {
|
||||||
tier = 'Junior Dev';
|
tier = 'Frontend Dev';
|
||||||
} else if (completedCourseIds.includes("f6daa88a-53d6-4901-8dbd-d2203a05b7ab")) {
|
} else if (completedCourseIds.includes("f6daa88a-53d6-4901-8dbd-d2203a05b7ab")) {
|
||||||
tier = 'New Dev';
|
tier = 'Plebdev';
|
||||||
} else if (session?.account?.provider === 'github') {
|
} else if (session?.account?.provider === 'github') {
|
||||||
tier = 'Pleb';
|
tier = 'Pleb';
|
||||||
}
|
}
|
||||||
@ -220,7 +221,7 @@ const UserProgress = () => {
|
|||||||
)}
|
)}
|
||||||
<span className={`text-lg ${task.completed ? 'text-white' : 'text-gray-400'}`}>{task.status}</span>
|
<span className={`text-lg ${task.completed ? 'text-white' : 'text-gray-400'}`}>{task.status}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="bg-blue-500 text-white text-xs px-2 py-1 rounded-full w-20 text-center">
|
<span className="bg-blue-500 text-white text-sm px-2 py-1 rounded-full w-24 text-center">
|
||||||
{task.tier}
|
{task.tier}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -277,9 +278,17 @@ const UserProgress = () => {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<button className="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-full font-semibold">
|
<button
|
||||||
View Badges (Coming Soon)
|
className="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-full font-semibold"
|
||||||
|
onClick={() => setShowBadges(true)}
|
||||||
|
>
|
||||||
|
View Badges
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<UserBadges
|
||||||
|
visible={showBadges}
|
||||||
|
onHide={() => setShowBadges(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
{
|
{
|
||||||
"kind": 30009,
|
"content":"",
|
||||||
"tags": [
|
"created_at":1733852920,
|
||||||
["d", "junior_dev_2024"],
|
"id":"b0a72bef2d167359e46f29371c6fab353364aded30dd04778e9c66b3e58def46",
|
||||||
["name", "Junior Developer 2024"],
|
"kind":30009,
|
||||||
["description", "Awarded upon completion of the Junior Developer course track"],
|
"pubkey":"62bad2c804210b9ccd8b3d6b49da7333185bae17c12b6d7a8ed5865642e82b1e",
|
||||||
["image", "https://yourplatform.com/badges/junior-dev.png", "1024x1024"],
|
"sig":"6b481176a7208b6f8edc76de1bf90859d3fe97b8894f49ee1fd2471ccf3584fb990e7e8a2bba075e6c9867e351c092d262c3fb67997c8c983c4deaef82adba8e",
|
||||||
["thumb", "https://yourplatform.com/badges/junior-dev_256x256.png", "256x256"],
|
"tags":[
|
||||||
["thumb", "https://yourplatform.com/badges/junior-dev_64x64.png", "64x64"]
|
["d","testr42069"],
|
||||||
]
|
["name","mario-test"],
|
||||||
|
["description","A test for mario, it's a' me."],
|
||||||
|
["image","https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/voltage-tipper.png","1024x1024"],
|
||||||
|
["thumb","https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/voltage-tipper.png","512x512"],
|
||||||
|
["thumb","https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/voltage-tipper.png","256x256"],
|
||||||
|
["thumb","https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/voltage-tipper.png","64x64"],
|
||||||
|
["thumb","https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/voltage-tipper.png","32x32"],
|
||||||
|
["thumb","https://plebdevs-bucket.nyc3.cdn.digitaloceanspaces.com/voltage-tipper.png","16x16"]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Key points for implementation:
|
Key points for implementation:
|
||||||
|
77
src/db/models/badgeModels.js
Normal file
77
src/db/models/badgeModels.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import prisma from "@/db/prisma";
|
||||||
|
|
||||||
|
export const getAllBadges = async () => {
|
||||||
|
return await prisma.badge.findMany({
|
||||||
|
include: {
|
||||||
|
course: true,
|
||||||
|
userBadges: {
|
||||||
|
include: {
|
||||||
|
user: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBadgeById = async (id) => {
|
||||||
|
return await prisma.badge.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
course: true,
|
||||||
|
userBadges: {
|
||||||
|
include: {
|
||||||
|
user: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBadgeByCourseId = async (courseId) => {
|
||||||
|
return await prisma.badge.findUnique({
|
||||||
|
where: { courseId },
|
||||||
|
include: {
|
||||||
|
course: true,
|
||||||
|
userBadges: {
|
||||||
|
include: {
|
||||||
|
user: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createBadge = async (data) => {
|
||||||
|
return await prisma.badge.create({
|
||||||
|
data: {
|
||||||
|
name: data.name,
|
||||||
|
noteId: data.noteId,
|
||||||
|
course: {
|
||||||
|
connect: { id: data.courseId }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
course: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateBadge = async (id, data) => {
|
||||||
|
return await prisma.badge.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
name: data.name,
|
||||||
|
noteId: data.noteId
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
course: true,
|
||||||
|
userBadges: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteBadge = async (id) => {
|
||||||
|
return await prisma.badge.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
};
|
@ -13,6 +13,7 @@ export const getAllCourses = async () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
purchases: true,
|
purchases: true,
|
||||||
|
badge: true
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -31,30 +32,41 @@ export const getCourseById = async (id) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
purchases: true,
|
purchases: true,
|
||||||
|
badge: true
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createCourse = async (data) => {
|
export const createCourse = async (data) => {
|
||||||
|
const { badge, ...courseData } = data;
|
||||||
return await prisma.course.create({
|
return await prisma.course.create({
|
||||||
data: {
|
data: {
|
||||||
id: data.id,
|
id: courseData.id,
|
||||||
noteId: data.noteId,
|
noteId: courseData.noteId,
|
||||||
price: data.price,
|
price: courseData.price,
|
||||||
user: { connect: { id: data.user.connect.id } },
|
user: { connect: { id: courseData.user.connect.id } },
|
||||||
lessons: {
|
lessons: {
|
||||||
connect: data.lessons.connect
|
connect: courseData.lessons.connect
|
||||||
}
|
},
|
||||||
|
...(badge && {
|
||||||
|
badge: {
|
||||||
|
create: {
|
||||||
|
name: badge.name,
|
||||||
|
noteId: badge.noteId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
lessons: true,
|
lessons: true,
|
||||||
user: true
|
user: true,
|
||||||
|
badge: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateCourse = async (id, data) => {
|
export const updateCourse = async (id, data) => {
|
||||||
const { lessons, ...otherData } = data;
|
const { lessons, badge, ...otherData } = data;
|
||||||
return await prisma.course.update({
|
return await prisma.course.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@ -66,7 +78,21 @@ export const updateCourse = async (id, data) => {
|
|||||||
draftId: lesson.draftId || null,
|
draftId: lesson.draftId || null,
|
||||||
index: index
|
index: index
|
||||||
}))
|
}))
|
||||||
}
|
},
|
||||||
|
...(badge && {
|
||||||
|
badge: {
|
||||||
|
upsert: {
|
||||||
|
create: {
|
||||||
|
name: badge.name,
|
||||||
|
noteId: badge.noteId
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: badge.name,
|
||||||
|
noteId: badge.noteId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
lessons: {
|
lessons: {
|
||||||
@ -77,12 +103,17 @@ export const updateCourse = async (id, data) => {
|
|||||||
orderBy: {
|
orderBy: {
|
||||||
index: 'asc'
|
index: 'asc'
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
badge: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteCourse = async (id) => {
|
export const deleteCourse = async (id) => {
|
||||||
|
await prisma.badge.deleteMany({
|
||||||
|
where: { courseId: id }
|
||||||
|
});
|
||||||
|
|
||||||
return await prisma.course.delete({
|
return await prisma.course.delete({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
|
64
src/db/models/userBadgeModels.js
Normal file
64
src/db/models/userBadgeModels.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import prisma from "@/db/prisma";
|
||||||
|
|
||||||
|
export const getUserBadges = async (userId) => {
|
||||||
|
return await prisma.userBadge.findMany({
|
||||||
|
where: { userId },
|
||||||
|
include: {
|
||||||
|
badge: true,
|
||||||
|
user: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserBadge = async (userId, badgeId) => {
|
||||||
|
return await prisma.userBadge.findUnique({
|
||||||
|
where: {
|
||||||
|
userId_badgeId: {
|
||||||
|
userId,
|
||||||
|
badgeId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
badge: true,
|
||||||
|
user: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const awardBadgeToUser = async (userId, badgeId) => {
|
||||||
|
return await prisma.userBadge.create({
|
||||||
|
data: {
|
||||||
|
user: {
|
||||||
|
connect: { id: userId }
|
||||||
|
},
|
||||||
|
badge: {
|
||||||
|
connect: { id: badgeId }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
badge: true,
|
||||||
|
user: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeUserBadge = async (userId, badgeId) => {
|
||||||
|
return await prisma.userBadge.delete({
|
||||||
|
where: {
|
||||||
|
userId_badgeId: {
|
||||||
|
userId,
|
||||||
|
badgeId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUsersWithBadge = async (badgeId) => {
|
||||||
|
return await prisma.userBadge.findMany({
|
||||||
|
where: { badgeId },
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
badge: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -20,6 +20,11 @@ export const getAllUsers = async () => {
|
|||||||
lesson: true,
|
lesson: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
userBadges: {
|
||||||
|
include: {
|
||||||
|
badge: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -47,6 +52,11 @@ export const getUserById = async (id) => {
|
|||||||
},
|
},
|
||||||
nip05: true,
|
nip05: true,
|
||||||
lightningAddress: true,
|
lightningAddress: true,
|
||||||
|
userBadges: {
|
||||||
|
include: {
|
||||||
|
badge: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -74,6 +84,11 @@ export const getUserByPubkey = async (pubkey) => {
|
|||||||
},
|
},
|
||||||
nip05: true,
|
nip05: true,
|
||||||
lightningAddress: true,
|
lightningAddress: true,
|
||||||
|
userBadges: {
|
||||||
|
include: {
|
||||||
|
badge: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -265,6 +280,11 @@ export const getUserByEmail = async (email) => {
|
|||||||
},
|
},
|
||||||
nip05: true,
|
nip05: true,
|
||||||
lightningAddress: true,
|
lightningAddress: true,
|
||||||
|
userBadges: {
|
||||||
|
include: {
|
||||||
|
badge: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
Any account type is backed by a nostr keypair:
|
|
||||||
- email (ephemeral keypair + email address)
|
|
||||||
- Github (ephemeral keypair + basic github account info and permissions to read data from API)
|
|
||||||
- anon (is only ephemeral keypair)
|
|
||||||
- Login with nostr (not ephemeral keypair, this is the users keypair, we only have access to private key through web extension interface)
|
|
||||||
|
|
||||||
Any time a user signs in, we try to pull the acount from the db, and add all of the data we can from the users record into their session.
|
|
||||||
If the user does not have an account in the db we create one for them and return it in a signed in state.
|
|
||||||
If the users does not have an account and they are signing up anon/github/email we must generate an ephemeral keypair for them and save it to the db (otherwise for nostr login user is bringing their keypair in whcih case we only need to save the pubkey)
|
|
||||||
|
|
||||||
Here is another consideration, when a user is signing in via nostr, we want to pull their latest kind0 info and treat that as the latest and greatest. If they have a record in the db we want to update it if the name or image has changed. If they do not have a record we create one with their nostr image and username (or first 8 chars of pubkey if there is no name)
|
|
||||||
|
|
||||||
Finally. It is possible to link github to an existing account in whcih case the user can sign in with either github or anon and it will pull the correct recrod.
|
|
Loading…
x
Reference in New Issue
Block a user