feat(wip): add support for multi user in instance

This commit is contained in:
Sonny
2023-05-30 01:17:50 +02:00
parent 7c4999830f
commit e7e7e0c950
8 changed files with 153 additions and 37 deletions

View File

@@ -0,0 +1,17 @@
/*
Warnings:
- You are about to drop the column `nextCategoryId` on the `category` table. All the data in the column will be lost.
- You are about to drop the column `nextLinkId` on the `link` table. All the data in the column will be lost.
- Added the required column `authorId` to the `category` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `category` DROP COLUMN `nextCategoryId`,
ADD COLUMN `authorId` INTEGER NOT NULL;
-- AlterTable
ALTER TABLE `link` DROP COLUMN `nextLinkId`;
-- AddForeignKey
ALTER TABLE `category` ADD CONSTRAINT `category_authorId_fkey` FOREIGN KEY (`authorId`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,11 @@
/*
Warnings:
- Added the required column `authorId` to the `link` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `link` ADD COLUMN `authorId` INTEGER NOT NULL;
-- AddForeignKey
ALTER TABLE `link` ADD CONSTRAINT `link_authorId_fkey` FOREIGN KEY (`authorId`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -8,12 +8,17 @@ generator client {
datasource db { datasource db {
provider = "mysql" provider = "mysql"
url = env("DATABASE_URL") url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
} }
model User { model User {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
google_id String @unique google_id String @unique
email String @unique email String @unique
categories Category[]
links Link[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -24,7 +29,10 @@ model Category {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
name String @unique @db.VarChar(255) name String @unique @db.VarChar(255)
links Link[] links Link[]
nextCategoryId Int @default(0)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -35,9 +43,13 @@ model Link {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
name String @db.VarChar(255) name String @db.VarChar(255)
url String @db.Text url String @db.Text
category Category @relation(fields: [categoryId], references: [id]) category Category @relation(fields: [categoryId], references: [id])
categoryId Int categoryId Int
nextLinkId Int @default(0)
author User @relation(fields: [authorId], references: [id])
authorId Int
favorite Boolean @default(false) favorite Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt

29
src/_middleware.ts Normal file
View File

@@ -0,0 +1,29 @@
import PATHS from "constants/paths";
export { default } from "next-auth/middleware";
// WAIT: for fix - Next.js@13.4.4 seems to be broken
// cf: https://github.com/nextauthjs/next-auth/issues/7650
// (this file must renamed "middleware.ts")
export const config = {
matcher: [
PATHS.HOME,
PATHS.LINK.CREATE,
PATHS.LINK.EDIT,
PATHS.LINK.REMOVE,
PATHS.CATEGORY.CREATE,
PATHS.CATEGORY.EDIT,
PATHS.CATEGORY.REMOVE,
PATHS.API.CATEGORY.CREATE,
PATHS.API.CATEGORY.EDIT,
PATHS.API.CATEGORY.REMOVE,
PATHS.API.LINK.CREATE,
PATHS.API.LINK.EDIT,
PATHS.API.LINK.REMOVE,
],
};

View File

@@ -1,5 +1,6 @@
const PATHS = { const PATHS = {
LOGIN: "/signin", LOGIN: "/signin",
LOGOUT: "/signout",
HOME: "/", HOME: "/",
CATEGORY: { CATEGORY: {
CREATE: "/category/create", CREATE: "/category/create",
@@ -13,14 +14,14 @@ const PATHS = {
}, },
API: { API: {
CATEGORY: { CATEGORY: {
CREATE: "/category/create", CREATE: "/api/category/create",
EDIT: "/category/edit", EDIT: "/api/category/edit",
REMOVE: "/category/remove", REMOVE: "/api/category/remove",
}, },
LINK: { LINK: {
CREATE: "/link/create", CREATE: "/api/link/create",
EDIT: "/link/edit", EDIT: "/api/link/edit",
REMOVE: "/link/remove", REMOVE: "/api/link/remove",
}, },
}, },
NOT_FOUND: "/404", NOT_FOUND: "/404",

View File

@@ -1,11 +1,12 @@
import { PrismaClient } from "@prisma/client"; import { PrismaClient } from "@prisma/client";
import PATHS from "constants/paths"; import PATHS from "constants/paths";
import NextAuth from "next-auth"; import NextAuth, { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google"; import GoogleProvider from "next-auth/providers/google";
const prisma = new PrismaClient(); const prisma = new PrismaClient();
export default NextAuth({ // TODO: refactor auth
export const authOptions = {
providers: [ providers: [
GoogleProvider({ GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID, clientId: process.env.GOOGLE_CLIENT_ID,
@@ -20,24 +21,33 @@ export default NextAuth({
}), }),
], ],
callbacks: { callbacks: {
async session({ session }) {
// check if stored in session still exist in db
await prisma.user.findFirstOrThrow({
where: { email: session.user.email },
});
return session;
},
async signIn({ account: accountParam, profile }) { async signIn({ account: accountParam, profile }) {
// TODO: Auth
console.log( console.log(
"Connexion via", "[AUTH]",
accountParam.provider, "User",
accountParam.providerAccountId, profile.name,
profile.email, "attempt to log in with",
profile.name accountParam.provider
); );
if (accountParam.provider !== "google") { if (accountParam.provider !== "google") {
console.log("[AUTH]", "User", profile.name, "rejeced : bad provider");
return ( return (
PATHS.LOGIN + PATHS.LOGIN +
"?error=" + "?error=" +
encodeURI("Authentitifcation via Google requise") encodeURI("Authentitifcation via Google requise")
); );
} }
const email = profile?.email; const email = profile?.email;
if (email === "") { if (email === "") {
console.log("[AUTH]", "User", profile.name, "rejeced : missing email");
return ( return (
PATHS.LOGIN + PATHS.LOGIN +
"?error=" + "?error=" +
@@ -48,6 +58,12 @@ export default NextAuth({
} }
const googleId = profile?.sub; const googleId = profile?.sub;
if (googleId === "") { if (googleId === "") {
console.log(
"[AUTH]",
"User",
profile.name,
"rejeced : missing google id"
);
return ( return (
PATHS.LOGIN + PATHS.LOGIN +
"?error=" + "?error=" +
@@ -74,6 +90,13 @@ export default NextAuth({
}); });
return true; return true;
} }
console.log(
"[AUTH]",
"User",
profile.name,
"rejeced : not authorized"
);
return ( return (
PATHS.LOGIN + PATHS.LOGIN +
"?error=" + "?error=" +
@@ -82,9 +105,11 @@ export default NextAuth({
) )
); );
} else { } else {
console.log("[AUTH]", "User", profile.name, "success");
return true; return true;
} }
} catch (error) { } catch (error) {
console.log("[AUTH]", "User", profile.name, "unhandled error");
console.error(error); console.error(error);
return ( return (
PATHS.LOGIN + PATHS.LOGIN +
@@ -97,8 +122,10 @@ export default NextAuth({
pages: { pages: {
signIn: PATHS.LOGIN, signIn: PATHS.LOGIN,
error: PATHS.LOGIN, error: PATHS.LOGIN,
signOut: PATHS.LOGOUT,
}, },
session: { session: {
maxAge: 60 * 60 * 6, // Session de 6 heures maxAge: 60 * 60 * 6, // Session de 6 heures
}, },
}); } as NextAuthOptions;
export default NextAuth(authOptions);

View File

@@ -1,10 +1,15 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]";
import prisma from "utils/prisma"; import prisma from "utils/prisma";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse
) { ) {
const session = await getServerSession(req, res, authOptions); // je sais plus d'où ça vient
console.log("session", session);
const name = req.body?.name as string; const name = req.body?.name as string;
if (!name) { if (!name) {
@@ -30,8 +35,20 @@ export default async function handler(
} }
try { try {
const { id: authorId } = await prisma.user.findFirst({
where: {
email: session.user.email,
},
select: {
id: true,
},
});
if (!authorId) {
throw new Error("Unable to find user");
}
const category = await prisma.category.create({ const category = await prisma.category.create({
data: { name }, data: { name, authorId },
}); });
return res.status(200).send({ return res.status(200).send({
success: "Catégorie créée avec succès", success: "Catégorie créée avec succès",

View File

@@ -1,5 +1,6 @@
import { getServerSession } from "next-auth/next";
import { Provider } from "next-auth/providers"; import { Provider } from "next-auth/providers";
import { getProviders, getSession, signIn } from "next-auth/react"; import { getProviders, signIn } from "next-auth/react";
import { NextSeo } from "next-seo"; import { NextSeo } from "next-seo";
import Link from "next/link"; import Link from "next/link";
@@ -7,6 +8,7 @@ import MessageManager from "components/MessageManager/MessageManager";
import PATHS from "constants/paths"; import PATHS from "constants/paths";
import styles from "styles/login.module.scss"; import styles from "styles/login.module.scss";
import { authOptions } from "./api/auth/[...nextauth]";
interface SignInProps { interface SignInProps {
providers: Provider[]; providers: Provider[];
@@ -36,8 +38,8 @@ export default function SignIn({ providers }: SignInProps) {
); );
} }
export async function getServerSideProps(context) { export async function getServerSideProps({ req, res }) {
const session = await getSession(context); const session = await getServerSession(req, res, authOptions);
if (session) { if (session) {
return { return {
redirect: { redirect: {