added tons of features
This commit is contained in:
59
actions/admin/category.ts
Normal file
59
actions/admin/category.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
'use server'
|
||||
|
||||
import {CategoryLocale} from '@prisma/client'
|
||||
import {z} from 'zod'
|
||||
|
||||
import {getCategoryBySlug} from '@/actions/model/category'
|
||||
import {i18nLocalesCodes} from '@/i18n-config'
|
||||
import {STORE_ID} from '@/lib/config/constants'
|
||||
import {db} from '@/lib/db/prisma/client'
|
||||
import {createCategoryFormSchema} from '@/lib/schemas/admin/category'
|
||||
import {cleanEmptyParams, dbErrorHandling, slug as slugger} from '@/lib/utils'
|
||||
|
||||
export const onCategoryCreateAction = async (
|
||||
formData: z.infer<typeof createCategoryFormSchema>
|
||||
) => {
|
||||
const validatedData = createCategoryFormSchema.parse(formData)
|
||||
|
||||
if (!validatedData) {
|
||||
return {error: 'Недійсні вхідні дані'}
|
||||
}
|
||||
|
||||
if (validatedData.locales.length < i18nLocalesCodes.length) {
|
||||
return {error: 'Заповніть всі мови'}
|
||||
}
|
||||
|
||||
const locales: CategoryLocale[] = []
|
||||
|
||||
for (const i in validatedData.locales) {
|
||||
const locale = validatedData.locales[i]
|
||||
|
||||
const {title, lang} = locale
|
||||
const slug = slugger(title, lang)
|
||||
const result = await getCategoryBySlug({slug, lang})
|
||||
|
||||
if (!result) {
|
||||
const normalized: any = cleanEmptyParams({slug, ...locale})
|
||||
locales.push(normalized)
|
||||
} else {
|
||||
return {error: `Категорія ${title} вже існує`}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const newCategory = await db.category.create({
|
||||
data: {
|
||||
storeId: STORE_ID,
|
||||
status: true,
|
||||
// position: 0,
|
||||
locales: {
|
||||
create: locales
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {success: JSON.stringify(newCategory, null, 2)}
|
||||
} catch (error) {
|
||||
return dbErrorHandling(error)
|
||||
}
|
||||
}
|
||||
129
actions/admin/product.ts
Normal file
129
actions/admin/product.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
'use server'
|
||||
|
||||
import {Meta, ProductLocale, ProductToStore} from '@prisma/client'
|
||||
import sanitizeHtml from 'sanitize-html'
|
||||
import {z} from 'zod'
|
||||
|
||||
import {i18nLocalesCodes} from '@/i18n-config'
|
||||
import {STORE_ID} from '@/lib/config/constants'
|
||||
import {getProductBySlug} from '@/lib/data/models/product'
|
||||
import {db} from '@/lib/db/prisma/client'
|
||||
import {createProductFormSchema} from '@/lib/schemas/admin/product'
|
||||
import {
|
||||
cleanEmptyParams,
|
||||
dbErrorHandling,
|
||||
isEmptyObj,
|
||||
slug as slugger
|
||||
} from '@/lib/utils'
|
||||
|
||||
export const onProductCreateAction = async (
|
||||
formData: z.infer<typeof createProductFormSchema>
|
||||
) => {
|
||||
const validatedData = createProductFormSchema.parse(formData)
|
||||
|
||||
if (!validatedData) return {error: 'Недійсні вхідні дані'}
|
||||
|
||||
if (validatedData.locales.length < i18nLocalesCodes.length) {
|
||||
return {error: 'Заповніть всі мови'}
|
||||
}
|
||||
|
||||
const {published, image} = validatedData
|
||||
const price = parseFloat(validatedData.price).toFixed(2)
|
||||
const price_promotional = parseFloat(
|
||||
validatedData.pricePromotional as string
|
||||
).toFixed(2)
|
||||
|
||||
if (parseFloat(price) < 1) {
|
||||
return {error: 'Ціна не може бути нижчою за одну гривню'}
|
||||
}
|
||||
|
||||
const meta: Meta[] = []
|
||||
|
||||
for (const i in validatedData.meta) {
|
||||
const normalizedMeta: any = cleanEmptyParams(validatedData.meta[i])
|
||||
|
||||
//if (!isEmptyObj(normalizedMeta)) {}
|
||||
meta.push(normalizedMeta)
|
||||
}
|
||||
|
||||
const locales: ProductLocale[] = []
|
||||
|
||||
for (const i in validatedData.locales) {
|
||||
const locale = validatedData.locales[i]
|
||||
const {title, lang} = locale
|
||||
const slug = slugger(title, lang)
|
||||
|
||||
const result = await getProductBySlug({slug, lang})
|
||||
|
||||
if (!result) {
|
||||
const normalized: any = cleanEmptyParams({slug, ...locale})
|
||||
|
||||
//locales.push({...normalized, meta: {create: meta[i]}})
|
||||
locales.push(normalized)
|
||||
} else {
|
||||
return {error: `Продукт з такою назвою ${title} вже існує`}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const newProduct = await db.product.create({
|
||||
data: {
|
||||
image,
|
||||
toStore: {
|
||||
create: {
|
||||
published,
|
||||
price,
|
||||
pricePromotional: price_promotional,
|
||||
storeId: STORE_ID
|
||||
}
|
||||
},
|
||||
locales: {
|
||||
create: locales
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {success: 'JSON.stringify(newProduct, null, 2)'}
|
||||
} catch (error) {
|
||||
return dbErrorHandling(error)
|
||||
}
|
||||
}
|
||||
|
||||
// const result = sanitizeHtml(description, {
|
||||
// allowedTags: [
|
||||
// 'p',
|
||||
// 'b',
|
||||
// 'i',
|
||||
// 'h1',
|
||||
// 'h2',
|
||||
// 'h3',
|
||||
// 'h4',
|
||||
// 'h5',
|
||||
// 'h6',
|
||||
// 'em',
|
||||
// 'strong',
|
||||
// 'a',
|
||||
// 'blockquote',
|
||||
// 'div',
|
||||
// 'li',
|
||||
// 'ol',
|
||||
// 'ul',
|
||||
// 'cite',
|
||||
// 'code',
|
||||
// 'small',
|
||||
// 'sub',
|
||||
// 'sup'
|
||||
// ],
|
||||
// nonBooleanAttributes: [],
|
||||
// allowedAttributes: {
|
||||
// a: ['href', 'name', 'target'],
|
||||
// img: ['src', 'srcset', 'alt', 'title', 'width', 'height', 'loading'],
|
||||
// selfClosing: ['img', 'hr']
|
||||
// },
|
||||
// allowedIframeHostnames: [],
|
||||
// parser: {
|
||||
// lowerCaseTags: true
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// console.log(result)
|
||||
21
actions/auth/common.ts
Normal file
21
actions/auth/common.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
'use server'
|
||||
|
||||
import {getUserWithAccount} from '@prisma/client/sql'
|
||||
|
||||
import {getAccountByUserId} from '@/data/accout'
|
||||
import {getUserById} from '@/data/user'
|
||||
import {db} from '@/lib/db/prisma/client'
|
||||
|
||||
export const exisingUser = async (id: string | number) => {
|
||||
return await getUserById(id)
|
||||
}
|
||||
|
||||
export const exisingUserAccount = async (userId: string | number) => {
|
||||
return await getAccountByUserId(userId)
|
||||
}
|
||||
|
||||
export const getUserAccountByUserId = async (userId: string | number) => {
|
||||
const user = await db.$queryRawTyped(getUserWithAccount(userId))
|
||||
|
||||
return user.length > 0 ? user[0] : null
|
||||
}
|
||||
16
actions/auth/google-login.ts
Normal file
16
actions/auth/google-login.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
'use server'
|
||||
|
||||
import {AuthError} from 'next-auth'
|
||||
|
||||
import {signIn} from '@/auth'
|
||||
|
||||
export async function googleAuthenticate() {
|
||||
try {
|
||||
await signIn('google')
|
||||
} catch (error) {
|
||||
if (error instanceof AuthError) {
|
||||
return 'google log in failed'
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
87
actions/auth/login.ts
Normal file
87
actions/auth/login.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
'use server'
|
||||
|
||||
import {User} from '@prisma/client'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import {AuthError} from 'next-auth'
|
||||
import {getTranslations} from 'next-intl/server'
|
||||
import * as z from 'zod'
|
||||
|
||||
import {signIn} from '@/auth'
|
||||
import {db} from '@/lib/db/prisma/client'
|
||||
import {LoginSchema} from '@/lib/schemas'
|
||||
|
||||
export const login = async (data: z.infer<typeof LoginSchema>) => {
|
||||
const t = await getTranslations('Auth')
|
||||
// Validate the input data
|
||||
const validatedData = LoginSchema.parse(data)
|
||||
|
||||
// If the data is invalid, return an error
|
||||
if (!validatedData) {
|
||||
return {error: t('Invalid input data')}
|
||||
}
|
||||
|
||||
// Destructure the validated data
|
||||
const {email, password} = validatedData
|
||||
|
||||
const userExists = await db.user.findFirst({
|
||||
where: {email}
|
||||
})
|
||||
|
||||
if (!userExists || !userExists.password || !userExists.email) {
|
||||
return {error: t('Error.user-not-found')}
|
||||
}
|
||||
|
||||
try {
|
||||
await signIn('credentials', {
|
||||
email: userExists.email as string,
|
||||
password: password as string,
|
||||
redirectTo: '/'
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof AuthError) {
|
||||
switch (error.type) {
|
||||
case 'CredentialsSignin':
|
||||
return {error: 'Invalid credentials'}
|
||||
default:
|
||||
return {error: error.type}
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
return {success: 'User successfully logged in!'}
|
||||
}
|
||||
|
||||
export const authorizeCallback = async (
|
||||
credentials: Partial<Record<'email' | 'password', unknown>>
|
||||
): Promise<User | null> => {
|
||||
const validatedData = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(6)
|
||||
})
|
||||
.safeParse(credentials)
|
||||
|
||||
if (!validatedData.success) return null
|
||||
|
||||
const {email, password} = validatedData.data
|
||||
|
||||
const user = await db.user.findFirst({
|
||||
where: {email}
|
||||
})
|
||||
|
||||
if (!user || !user.password || !user.email) return null
|
||||
|
||||
try {
|
||||
if (await bcrypt.compare(password, user.password)) {
|
||||
return user
|
||||
} else {
|
||||
console.log('Invalid credentials', user.email)
|
||||
return null
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Verifying password error', err)
|
||||
}
|
||||
return null
|
||||
}
|
||||
79
actions/auth/register.ts
Normal file
79
actions/auth/register.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
'use server'
|
||||
|
||||
import bcrypt from 'bcryptjs'
|
||||
import * as z from 'zod'
|
||||
|
||||
import {db} from '@/lib/db/prisma/client'
|
||||
import {RegisterSchema} from '@/lib/schemas'
|
||||
import {hashPassword} from '@/lib/utils'
|
||||
|
||||
// import { generateVerificationToken } from "@/lib/token";
|
||||
// import { sendVerificationEmail } from "@/lib/mail";
|
||||
|
||||
export const register = async (data: z.infer<typeof RegisterSchema>) => {
|
||||
try {
|
||||
// Validate the input data
|
||||
const validatedData = RegisterSchema.parse(data)
|
||||
|
||||
// If the data is invalid, return an error
|
||||
if (!validatedData) {
|
||||
return {error: 'Invalid input data'}
|
||||
}
|
||||
|
||||
// Destructure the validated data
|
||||
const {email, name, password, passwordConfirmation} = validatedData
|
||||
|
||||
// Check if passwords match
|
||||
if (password !== passwordConfirmation) {
|
||||
return {error: 'Passwords do not match'}
|
||||
}
|
||||
|
||||
// Hash the password
|
||||
const hashedPassword = await hashPassword(password)
|
||||
|
||||
// Check to see if user already exists
|
||||
const userExists = await db.user.findFirst({
|
||||
where: {
|
||||
email
|
||||
}
|
||||
})
|
||||
|
||||
// If the user exists, return an error
|
||||
if (userExists) {
|
||||
return {error: 'Email already is in use. Please try another one.'}
|
||||
}
|
||||
|
||||
const lowerCaseEmail = email.toLowerCase()
|
||||
|
||||
// Create the user
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: lowerCaseEmail,
|
||||
name,
|
||||
password: hashedPassword
|
||||
}
|
||||
})
|
||||
|
||||
// Generate Verification Token
|
||||
// const verificationToken = await generateVerificationToken(email);
|
||||
|
||||
// await sendVerificationEmail(lowerCaseEmail, verificationToken.token);
|
||||
|
||||
return {success: 'Email Verification was sent'}
|
||||
} catch (error) {
|
||||
// Handle the error, specifically check for a 503 error
|
||||
console.error('Database error:', error)
|
||||
|
||||
if ((error as {code: string}).code === 'ETIMEDOUT') {
|
||||
return {
|
||||
error: 'Unable to connect to the database. Please try again later.'
|
||||
}
|
||||
} else if ((error as {code: string}).code === '503') {
|
||||
return {
|
||||
error: 'Service temporarily unavailable. Please try again later.'
|
||||
}
|
||||
} else {
|
||||
return {error: 'An unexpected error occurred. Please try again later.'}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
actions/execute-action.helper.ts
Normal file
31
actions/execute-action.helper.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {isRedirectError} from 'next/dist/client/components/redirect-error'
|
||||
|
||||
type Options<T> = {
|
||||
actionFn: () => Promise<T>
|
||||
successMessage?: string
|
||||
}
|
||||
|
||||
const executeAction = async <T>({
|
||||
actionFn,
|
||||
successMessage = 'The actions was successful'
|
||||
}: Options<T>): Promise<{success: boolean; message: string}> => {
|
||||
try {
|
||||
await actionFn()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: successMessage
|
||||
}
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: 'An error has occurred during executing the action'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {executeAction}
|
||||
17
actions/model/category.ts
Normal file
17
actions/model/category.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
'use server'
|
||||
|
||||
import {CategoryLocale, Lang} from '@prisma/client'
|
||||
|
||||
import {db} from '@/lib/db/prisma/client'
|
||||
|
||||
export const getCategoryBySlug = async (data: {
|
||||
slug: string
|
||||
lang: string
|
||||
}): Promise<CategoryLocale | null> => {
|
||||
return db.categoryLocale.findFirst({
|
||||
where: {
|
||||
slug: data.slug,
|
||||
lang: data.lang as Lang
|
||||
}
|
||||
})
|
||||
}
|
||||
30
actions/permission/index.ts
Normal file
30
actions/permission/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
'use server'
|
||||
|
||||
import {auth} from '@/auth'
|
||||
import {
|
||||
Access,
|
||||
AllRolesPermissions,
|
||||
PERMISSIONS,
|
||||
type Permission,
|
||||
type SingedInSession
|
||||
} from '@/lib/permission'
|
||||
|
||||
export type CanAccessResponse = {can: boolean; session: SingedInSession | null}
|
||||
export type CanResponse = boolean | CanAccessResponse
|
||||
|
||||
const can = async (permission: Permission): Promise<CanResponse> => {
|
||||
const session: SingedInSession = (await auth()) as SingedInSession
|
||||
|
||||
if (!session) return false
|
||||
|
||||
const able =
|
||||
PERMISSIONS[session.user.role as keyof AllRolesPermissions].includes(
|
||||
permission
|
||||
)
|
||||
|
||||
return !Object.values(Access).includes(permission as Access)
|
||||
? able
|
||||
: {can: able, session}
|
||||
}
|
||||
|
||||
export default can
|
||||
Reference in New Issue
Block a user