Compare commits
	
		
			7 Commits
		
	
	
		
			f047a9ec1c
			...
			b7d144bb66
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | b7d144bb66 | ||
|   | 753abe7607 | ||
|   | 8ee42ea0ac | ||
|   | a4c0abc10f | ||
|   | 021695c5b1 | ||
|   | 3b9ee4dd5b | ||
|   | 2c1938e62a | 
							
								
								
									
										3
									
								
								.env.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.env.example
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | NEXT_PUBLIC_CHINT_TOKEN=lIywwAMdrOdsRxuWvRoekdxrPtmIPkxA | ||||||
|  | DATABASE_URL="postgresql://postgres:root@localhost:5432/rooftop?schema=public" | ||||||
|  | JWT_SECRET="secret_key" | ||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -34,3 +34,5 @@ yarn-error.log* | |||||||
| # typescript | # typescript | ||||||
| *.tsbuildinfo | *.tsbuildinfo | ||||||
| next-env.d.ts | next-env.d.ts | ||||||
|  | 
 | ||||||
|  | .env | ||||||
|  | |||||||
| @ -64,34 +64,6 @@ const InverterViewPage = (props: Props) => { | |||||||
|                                     </button> |                                     </button> | ||||||
|                                 )} |                                 )} | ||||||
|                             </Tab> |                             </Tab> | ||||||
|                             <Tab as={Fragment}> |  | ||||||
|                                 {({ selected }) => ( |  | ||||||
|                                     <button className={`${selected ? 'border-b !border-primary text-primary !outline-none' : ''} -mb-[1px] flex items-center border-transparent p-5 py-3 before:inline-block hover:border-b hover:!border-primary hover:text-primary`} > |  | ||||||
|                                         Event |  | ||||||
|                                     </button> |  | ||||||
|                                 )} |  | ||||||
|                             </Tab> |  | ||||||
|                             <Tab as={Fragment}> |  | ||||||
|                                 {({ selected }) => ( |  | ||||||
|                                     <button className={`${selected ? 'border-b !border-primary text-primary !outline-none' : ''} -mb-[1px] flex items-center border-transparent p-5 py-3 before:inline-block hover:border-b hover:!border-primary hover:text-primary`} > |  | ||||||
|                                         Settings |  | ||||||
|                                     </button> |  | ||||||
|                                 )} |  | ||||||
|                             </Tab> |  | ||||||
|                             <Tab as={Fragment}> |  | ||||||
|                                 {({ selected }) => ( |  | ||||||
|                                     <button className={`${selected ? 'border-b !border-primary text-primary !outline-none' : ''} -mb-[1px] flex items-center border-transparent p-5 py-3 before:inline-block hover:border-b hover:!border-primary hover:text-primary`} > |  | ||||||
|                                         Firmware |  | ||||||
|                                     </button> |  | ||||||
|                                 )} |  | ||||||
|                             </Tab> |  | ||||||
|                             <Tab as={Fragment}> |  | ||||||
|                                 {({ selected }) => ( |  | ||||||
|                                     <button className={`${selected ? 'border-b !border-primary text-primary !outline-none' : ''} -mb-[1px] flex items-center border-transparent p-5 py-3 before:inline-block hover:border-b hover:!border-primary hover:text-primary`} > |  | ||||||
|                                         Data |  | ||||||
|                                     </button> |  | ||||||
|                                 )} |  | ||||||
|                             </Tab> |  | ||||||
|                         </Tab.List> |                         </Tab.List> | ||||||
|                         <Tab.Panels> |                         <Tab.Panels> | ||||||
|                             <Tab.Panel> |                             <Tab.Panel> | ||||||
| @ -118,9 +90,8 @@ const InverterViewPage = (props: Props) => { | |||||||
|                                     </div> |                                     </div> | ||||||
| 
 | 
 | ||||||
|                                 </div> |                                 </div> | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|                             </Tab.Panel> |                             </Tab.Panel> | ||||||
|  |                             <Tab.Panel>Chart</Tab.Panel> | ||||||
|                         </Tab.Panels> |                         </Tab.Panels> | ||||||
|                     </Tab.Group> |                     </Tab.Group> | ||||||
|                 )} |                 )} | ||||||
|  | |||||||
| @ -100,12 +100,30 @@ const Sidebar = () => { | |||||||
|                             </li> |                             </li> | ||||||
| 
 | 
 | ||||||
|                             <li className="menu nav-item"> |                             <li className="menu nav-item"> | ||||||
|                                 <Link href="#" className="nav-link group"> |                                 <button type="button" className={`${currentMenu === 'sungrow' ? 'active' : ''} nav-link group w-full`} onClick={() => toggleMenu('sungrow')}> | ||||||
|                                     <div className="flex items-center"> |                                     <div className="flex items-center"> | ||||||
|                                         <IconMenuComponents className="shrink-0 group-hover:!text-primary" /> |                                         <IconMenuComponents className="shrink-0 group-hover:!text-primary" /> | ||||||
|                                         <span className="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">Sungrow</span> |                                         <span className="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">Sungrow</span> | ||||||
|                                     </div> |                                     </div> | ||||||
|                                 </Link> | 
 | ||||||
|  |                                     <div className={currentMenu !== 'component' ? '-rotate-90 rtl:rotate-90' : ''}> | ||||||
|  |                                         <IconCaretDown /> | ||||||
|  |                                     </div> | ||||||
|  |                                 </button> | ||||||
|  | 
 | ||||||
|  |                                 <AnimateHeight duration={300} height={currentMenu === 'sungrow' ? 'auto' : 0}> | ||||||
|  |                                     <ul className="sub-menu text-gray-500"> | ||||||
|  |                                         <li> | ||||||
|  |                                             <Link href="/sungrow/plant">Plant</Link> | ||||||
|  |                                         </li> | ||||||
|  |                                         <li> | ||||||
|  |                                             <Link href="/sungrow/device">Device</Link> | ||||||
|  |                                         </li> | ||||||
|  |                                         <li> | ||||||
|  |                                             <Link href="/sungrow/maintenance">Maintenance</Link> | ||||||
|  |                                         </li> | ||||||
|  |                                     </ul> | ||||||
|  |                                 </AnimateHeight> | ||||||
|                             </li> |                             </li> | ||||||
| 
 | 
 | ||||||
|                             <li className="menu nav-item"> |                             <li className="menu nav-item"> | ||||||
|  | |||||||
							
								
								
									
										20
									
								
								http/auth.http
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								http/auth.http
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | GET http://localhost:3005/api/auth/me | ||||||
|  | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2YmM2MGIzMC1hNzcyLTRiM2MtOTYwOC1jMjgyNmNhMjA2NjEiLCJlbWFpbCI6ImFzZEBhc2QuY29tIiwiaWF0IjoxNzQwNTUyMjE4LCJleHAiOjE3NDA1NTU4MTh9.n3rFlFbqKY-kg8YF6cuRkT_Kc6hsCqQQ87TJbkS8DAg | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | POST http://localhost:3005/api/register | ||||||
|  | Content-Type: application/json | ||||||
|  | 
 | ||||||
|  | { | ||||||
|  |     "email": "asd@asd.com", | ||||||
|  |     "password":"123456" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | POST http://localhost:3005/api/login | ||||||
|  | Content-Type: application/json | ||||||
|  | 
 | ||||||
|  | { | ||||||
|  |     "email": "asd@asd.com", | ||||||
|  |     "password":"123456" | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								middleware.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								middleware.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | import { NextRequest, NextResponse } from "next/server"; | ||||||
|  | import jwt from "jsonwebtoken"; | ||||||
|  | 
 | ||||||
|  | const SECRET_KEY = process.env.JWT_SECRET as string; | ||||||
|  | 
 | ||||||
|  | export function middleware(req: NextRequest) { | ||||||
|  |     const token = req.cookies.get("token")?.value; | ||||||
|  | 
 | ||||||
|  |     if (!token) return NextResponse.redirect(new URL("/login", req.url)); | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         jwt.verify(token, SECRET_KEY); | ||||||
|  |         return NextResponse.next(); | ||||||
|  |     } catch (error) { | ||||||
|  |         return NextResponse.redirect(new URL("/login", req.url)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const config = { matcher: ["/dashboard", "/profile"] }; | ||||||
							
								
								
									
										893
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										893
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -11,6 +11,7 @@ | |||||||
|     "dependencies": { |     "dependencies": { | ||||||
|         "@emotion/react": "^11.10.6", |         "@emotion/react": "^11.10.6", | ||||||
|         "@headlessui/react": "^1.7.8", |         "@headlessui/react": "^1.7.8", | ||||||
|  |         "@prisma/client": "^6.4.1", | ||||||
|         "@reduxjs/toolkit": "^1.9.1", |         "@reduxjs/toolkit": "^1.9.1", | ||||||
|         "@tippyjs/react": "^4.2.6", |         "@tippyjs/react": "^4.2.6", | ||||||
|         "@types/node": "18.11.18", |         "@types/node": "18.11.18", | ||||||
| @ -18,9 +19,12 @@ | |||||||
|         "@types/react-dom": "18.0.10", |         "@types/react-dom": "18.0.10", | ||||||
|         "apexcharts": "^4.5.0", |         "apexcharts": "^4.5.0", | ||||||
|         "axios": "^1.7.9", |         "axios": "^1.7.9", | ||||||
|  |         "bcrypt": "^5.1.1", | ||||||
|  |         "cookie": "^1.0.2", | ||||||
|         "eslint": "8.32.0", |         "eslint": "8.32.0", | ||||||
|         "eslint-config-next": "13.1.2", |         "eslint-config-next": "13.1.2", | ||||||
|         "i18next": "^22.4.10", |         "i18next": "^22.4.10", | ||||||
|  |         "jsonwebtoken": "^9.0.2", | ||||||
|         "next": "14.0.3", |         "next": "14.0.3", | ||||||
|         "ni18n": "^1.0.5", |         "ni18n": "^1.0.5", | ||||||
|         "react": "18.2.0", |         "react": "18.2.0", | ||||||
| @ -31,7 +35,6 @@ | |||||||
|         "react-perfect-scrollbar": "^1.5.8", |         "react-perfect-scrollbar": "^1.5.8", | ||||||
|         "react-popper": "^2.3.0", |         "react-popper": "^2.3.0", | ||||||
|         "react-redux": "^8.1.3", |         "react-redux": "^8.1.3", | ||||||
|         "typescript": "4.9.4", |  | ||||||
|         "universal-cookie": "^6.1.1", |         "universal-cookie": "^6.1.1", | ||||||
|         "yup": "^0.32.11" |         "yup": "^0.32.11" | ||||||
|     }, |     }, | ||||||
| @ -44,6 +47,7 @@ | |||||||
|         "postcss": "^8.4.35", |         "postcss": "^8.4.35", | ||||||
|         "prettier": "^2.8.0", |         "prettier": "^2.8.0", | ||||||
|         "prettier-plugin-tailwindcss": "^0.2.0", |         "prettier-plugin-tailwindcss": "^0.2.0", | ||||||
|         "tailwindcss": "^3.4.1" |         "tailwindcss": "^3.4.1", | ||||||
|  |         "typescript": "^5.7.3" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										27
									
								
								pages/api/auth/me.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								pages/api/auth/me.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | import { NextApiRequest, NextApiResponse } from "next"; | ||||||
|  | import jwt from "jsonwebtoken"; | ||||||
|  | import { PrismaClient } from "@prisma/client"; | ||||||
|  | 
 | ||||||
|  | const prisma = new PrismaClient(); | ||||||
|  | const SECRET_KEY = process.env.JWT_SECRET as string; | ||||||
|  | 
 | ||||||
|  | export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||||||
|  |   const authHeader = req.headers.authorization; | ||||||
|  | 
 | ||||||
|  |   if (!authHeader || !authHeader.startsWith("Bearer ")) { | ||||||
|  |     return res.status(401).json({ message: "Unauthorized" }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const token = authHeader.split(" ")[1]; // Extract token
 | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     const decoded: any = jwt.verify(token, SECRET_KEY); | ||||||
|  |     const user = await prisma.user.findUnique({ where: { id: decoded.userId } }); | ||||||
|  | 
 | ||||||
|  |     if (!user) return res.status(401).json({ message: "User not found" }); | ||||||
|  | 
 | ||||||
|  |     res.json({ user }); | ||||||
|  |   } catch (error) { | ||||||
|  |     res.status(401).json({ message: "Invalid token" }); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								pages/api/login.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								pages/api/login.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | import { NextApiRequest, NextApiResponse } from "next"; | ||||||
|  | import { PrismaClient } from "@prisma/client"; | ||||||
|  | import bcrypt from "bcrypt"; | ||||||
|  | import jwt from "jsonwebtoken"; | ||||||
|  | 
 | ||||||
|  | const prisma = new PrismaClient(); | ||||||
|  | const SECRET_KEY = process.env.JWT_SECRET as string; | ||||||
|  | 
 | ||||||
|  | export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||||||
|  |     if (req.method !== "POST") return res.status(405).json({ message: "Method not allowed" }); | ||||||
|  | 
 | ||||||
|  |     const { email, password } = req.body; | ||||||
|  | 
 | ||||||
|  |     const user = await prisma.user.findUnique({ where: { email } }); | ||||||
|  |     if (!user) return res.status(401).json({ message: "Invalid credentials" }); | ||||||
|  | 
 | ||||||
|  |     const isMatch = await bcrypt.compare(password, user.password); | ||||||
|  |     if (!isMatch) return res.status(401).json({ message: "Invalid credentials" }); | ||||||
|  | 
 | ||||||
|  |     const token = jwt.sign({ userId: user.id, email: user.email }, SECRET_KEY, { expiresIn: "1h" }); | ||||||
|  | 
 | ||||||
|  |     res.setHeader("Set-Cookie", `token=${token}; HttpOnly; Path=/; Secure`); | ||||||
|  |     res.json({ token }); | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								pages/api/register.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								pages/api/register.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | import { NextApiRequest, NextApiResponse } from "next"; | ||||||
|  | import { PrismaClient } from "@prisma/client"; | ||||||
|  | import bcrypt from "bcrypt"; | ||||||
|  | 
 | ||||||
|  | const prisma = new PrismaClient(); | ||||||
|  | 
 | ||||||
|  | export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||||||
|  |     if (req.method !== "POST") return res.status(405).json({ message: "Method not allowed" }); | ||||||
|  | 
 | ||||||
|  |     const { email, password } = req.body; | ||||||
|  | 
 | ||||||
|  |     const existingUser = await prisma.user.findUnique({ where: { email } }); | ||||||
|  |     if (existingUser) return res.status(400).json({ message: "User already exists" }); | ||||||
|  | 
 | ||||||
|  |     const hashedPassword = await bcrypt.hash(password, 10); | ||||||
|  |     const user = await prisma.user.create({ | ||||||
|  |         data: { email, password: hashedPassword }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     res.status(201).json({ message: "User registered", user }); | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								prisma/migrations/20250226061632_init/migration.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								prisma/migrations/20250226061632_init/migration.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | -- CreateTable | ||||||
|  | CREATE TABLE "User" ( | ||||||
|  |     "id" TEXT NOT NULL, | ||||||
|  |     "email" TEXT NOT NULL, | ||||||
|  |     "password" TEXT NOT NULL, | ||||||
|  |     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||||
|  | 
 | ||||||
|  |     CONSTRAINT "User_pkey" PRIMARY KEY ("id") | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- CreateIndex | ||||||
|  | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); | ||||||
							
								
								
									
										3
									
								
								prisma/migrations/migration_lock.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								prisma/migrations/migration_lock.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | # Please do not edit this file manually | ||||||
|  | # It should be added in your version-control system (e.g., Git) | ||||||
|  | provider = "postgresql" | ||||||
							
								
								
									
										22
									
								
								prisma/schema.prisma
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								prisma/schema.prisma
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | // This is your Prisma schema file, | ||||||
|  | // learn more about it in the docs: https://pris.ly/d/prisma-schema | ||||||
|  | 
 | ||||||
|  | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? | ||||||
|  | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init | ||||||
|  | 
 | ||||||
|  | generator client { | ||||||
|  |     provider = "prisma-client-js" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | datasource db { | ||||||
|  |     provider = "postgresql" | ||||||
|  |     url      = env("DATABASE_URL") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | model User { | ||||||
|  |     id       String  @id @default(uuid()) | ||||||
|  |     email    String  @unique | ||||||
|  |     password String | ||||||
|  |     createdAt DateTime @default(now()) | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user