Connect Meshkee backend, blog and shared account session

This commit is contained in:
Alireza Hassani
2026-09-05 19:41:56 +03:30
parent ee4b384238
commit de46b50e13
33 changed files with 803 additions and 11 deletions
+35
View File
@@ -0,0 +1,35 @@
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
import { api, ApiError, getTenant } from '../../../lib/meshkee/api';
import { ACCESS_COOKIE,REFRESH_COOKIE,setSessionCookies } from '../../../lib/meshkee/session';
type BusinessMembership={businessId?:number|string;id?:number|string;isOwner?:boolean;teamRole?:'admin'|'editor'|'viewer'|null};
type User={firstName?:string;lastName?:string;businesses?:BusinessMembership[];isSuperAdmin?:boolean;roles?:string[]};
const headers={'Cache-Control':'no-store'};
function hasWebsiteAccess(user:User, businessId:number|string){
if(user.isSuperAdmin||user.roles?.includes('super_admin'))return true;
return user.businesses?.some(membership=>
String(membership.businessId??membership.id)===String(businessId)
&& (membership.isOwner===true||membership.teamRole==='admin'||membership.teamRole==='editor'||membership.teamRole==='viewer')
)??false;
}
export async function GET(request:Request){
const jar=await cookies();const token=jar.get(ACCESS_COOKIE)?.value;const refreshToken=jar.get(REFRESH_COOKIE)?.value;
if(!token&&!refreshToken)return NextResponse.json({user:null},{headers});
let refreshed:{accessToken:string;refreshToken:string}|undefined;
try{
let result:{user:User};
try{
if(!token)throw new ApiError(401);
result=await api<{user:User}>('/auth/me',{headers:{Authorization:`Bearer ${token}`}});
}catch(error){
if(!(error instanceof ApiError)||error.status!==401||!refreshToken)throw error;
refreshed=await api<{accessToken:string;refreshToken:string}>('/auth/refresh',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({refreshToken})});
if(!refreshed.accessToken||!refreshed.refreshToken)throw new Error('Invalid refresh response');
result=await api<{user:User}>('/auth/me',{headers:{Authorization:`Bearer ${refreshed.accessToken}`}});
}
const tenant=await getTenant();
const response=NextResponse.json({user:{name:[result.user.firstName,result.user.lastName].filter(Boolean).join(' ')||'کاربر',isAdmin:hasWebsiteAccess(result.user,tenant.id)}},{headers});
if(refreshed)setSessionCookies(response,request,refreshed);
return response;
}catch(error){return NextResponse.json({user:null},{status:error instanceof ApiError&&error.status===401?401:503,headers})}
}
+18
View File
@@ -0,0 +1,18 @@
import {sameOrigin} from '../../../lib/meshkee/session';
import { tenantApi } from '../../../lib/meshkee/api';
import { validateContact } from '../../../lib/meshkee/contact';
export async function POST(request:Request) {
if(!sameOrigin(request))return Response.json({message:'درخواست نامعتبر است.'},{status:403});
if(!request.headers.get('content-type')?.includes('application/json'))return Response.json({message:'فرمت درخواست نامعتبر است.'},{status:415});
const raw=await request.text();
if(raw.length>20000)return Response.json({message:'متن پیام بیش از حد طولانی است.'},{status:413});
let input;
try{input=JSON.parse(raw)}catch{return Response.json({message:'درخواست نامعتبر است.'},{status:400})}
if(!input||typeof input!=='object'||Array.isArray(input))return Response.json({message:'درخواست نامعتبر است.'},{status:400});
const {data,errors}=validateContact(input);
if(Object.keys(errors).length)return Response.json({message:'اطلاعات فرم را بررسی کنید.',errors},{status:400});
try {
await tenantApi('/contact-submissions',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(Object.fromEntries(Object.entries(data).filter(([,value])=>value)))});
return Response.json({message:'پیام شما با موفقیت ثبت شد. از ارتباط شما سپاسگزاریم.'},{status:201});
}catch{return Response.json({message:'پیام ارسال نشد. لطفاً دوباره تلاش کنید یا با ما تماس بگیرید.'},{status:502})}
}
+12
View File
@@ -0,0 +1,12 @@
import {NextResponse} from 'next/server';
import { ACCESS_COOKIE,REFRESH_COOKIE,cookieOptions,sameOrigin } from '../../../lib/meshkee/session';
export async function POST(request:Request){
if(!sameOrigin(request))return NextResponse.json({message:'درخواست نامعتبر است.'},{status:403});
const response=NextResponse.json({ok:true});
for(const name of [ACCESS_COOKIE,REFRESH_COOKIE]){
response.cookies.set(name,'',{...cookieOptions(request),maxAge:0});
// Also expire a host-only cookie left by a local/older deployment.
if(cookieOptions(request).domain)response.headers.append('Set-Cookie',`${name}=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax`);
}
return response;
}