Remove server folder files.

Update nuxt.config
Add new composables.
This commit is contained in:
mustafa-rezae 2025-05-08 14:38:30 +03:30
parent 2e0d14ab61
commit ff3469a271
17 changed files with 215 additions and 164 deletions

View File

@ -1,4 +1,5 @@
NUXT_PUBLIC_BASE_URL=http://192.168.23.161/
# NUXT_PUBLIC_BASE_URL=http://192.168.23.161/
NUXT_PUBLIC_BASE_URL=https://hadithai.ir/
VITE_APP_NAME=Hadith

11
composables/useApi.ts Normal file
View File

@ -0,0 +1,11 @@
import type { UseFetchOptions } from 'nuxt/app'
export function useApi<T>(
url: string | (() => string),
options?: UseFetchOptions<T>,
) {
return useFetch(url, {
...options,
$fetch: useNuxtApp().$api as typeof $fetch
})
}

View File

@ -0,0 +1,81 @@
import type { FetchOptions } from 'ofetch'
import { useStorage } from '@vueuse/core'
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
interface HttpRequestOptions<T = unknown> extends FetchOptions {
data?: T
}
export const useHttpService = () => {
const config = useRuntimeConfig()
const token = useStorage('id_token', 'GuestAccess')
/**
* Constructs the full API URL
*/
const getFullUrl = (endpoint: string): string => {
return `${config.public.NUXT_PUBLIC_BASE_URL}${config.public.NUXT_PUBLIC_API_NAME}${endpoint}`
}
/**
* Base fetch method with common configuration
*/
const baseFetch = async <T = any>(
method: HttpMethod,
url: string,
options: HttpRequestOptions = {}
) => {
const headers = {
...options.headers,
...(token.value ? { Authorization: token.value } : {})
}
try {
const response = await $fetch<T>(getFullUrl(url), {
method,
headers,
...options,
body: options.data || options.body
})
return response
} catch (error) {
// Handle errors globally or rethrow for specific handling
console.error(`HTTP ${method} error for ${url}:`, error)
throw error
}
}
return {
/**
* GET request
*/
getRequest: <T = any>(url: string, options?: HttpRequestOptions) =>
baseFetch<T>('GET', url, options),
/**
* POST request
*/
postRequest: <T = any>(url: string, data?: any, options?: HttpRequestOptions) =>
baseFetch<T>('POST', url, { ...options, data }),
/**
* PUT request
*/
putRequest: <T = any>(url: string, data?: any, options?: HttpRequestOptions) =>
baseFetch<T>('PUT', url, { ...options, data }),
/**
* PATCH request
*/
patchRequest: <T = any>(url: string, data?: any, options?: HttpRequestOptions) =>
baseFetch<T>('PATCH', url, { ...options, data }),
/**
* DELETE request
*/
deleteRequest: <T = any>(url: string, options?: HttpRequestOptions) =>
baseFetch<T>('DELETE', url, options)
}
}

View File

@ -0,0 +1,49 @@
// composables/useInfiniteScroll.ts
export default function useInfiniteScroll(
callback: () => void,
elementId: string
) {
const isFetching = ref(false);
const infiniteScroll = ref<HTMLElement | null>(null);
const handleScroll = () => {
if (isFetching.value) return;
const scrollPosition =
infiniteScroll.value.scrollTop + infiniteScroll.value.clientHeight;
const threshold = infiniteScroll.value.scrollHeight - 100;
if (scrollPosition >= threshold) {
isFetching.value = true;
callback().finally(() => {
isFetching.value = false;
});
}
};
const handleTouchEnd = () => {
// Add a slight delay to ensure scroll position is updated
setTimeout(handleScroll, 100);
};
onMounted(() => {
const targetElement = document.getElementById(elementId);
infiniteScroll.value = targetElement;
if (targetElement) {
targetElement.addEventListener("scroll", handleScroll);
targetElement.addEventListener("touchend", handleTouchEnd);
}
});
onBeforeUnmount(() => {
const targetElement = document.getElementById(elementId);
infiniteScroll.value = targetElement;
if (targetElement) {
targetElement.removeEventListener("scroll", handleScroll);
targetElement.removeEventListener("touchend", handleTouchEnd);
}
});
return { isFetching };
}

View File

@ -0,0 +1,46 @@
// composables/useInfiniteScrollObserver.ts
import { onBeforeUnmount, onMounted, ref } from "vue";
export default function useInfiniteScrollObserver(callback) {
const observer = ref<IntersectionObserver | null>(null);
const isFetching = ref(false);
const infiniteScroll = ref<HTMLElement | null>(null);
console.info("useInfiniteScrollObserver");
const initObserver = () => {
console.info("useInfiniteScrollObserver");
observer.value = new IntersectionObserver(
(entries) => {
console.info("useInfiniteScrollObserver");
if (entries[0].isIntersecting && !isFetching.value) {
isFetching.value = true;
callback().finally(() => {
isFetching.value = false;
});
}
},
{
rootMargin: "200px", // Load when 200px away from viewport
threshold: 0.1,
}
);
if (infiniteScroll.value) {
observer.value.observe(infiniteScroll.value);
}
};
onMounted(() => {
initObserver();
});
onBeforeUnmount(() => {
if (observer.value && infiniteScroll.value) {
observer.value.unobserve(infiniteScroll.value);
}
});
return { isFetching, infiniteScroll };
}

View File

@ -2,13 +2,16 @@ FROM node:22
WORKDIR /app
COPY package*.json ./
COPY . .
RUN npm install
COPY . .
RUN npm run build-haditha
RUN npm install -g pm2
RUN pm2 start .output/server/index.mjs --name "nuxt-app"
RUN pm2 save
RUN pm2 startup
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
CMD ["pm2-runtime", ".output/server/index.mjs"]

View File

@ -70,7 +70,7 @@ export default defineNuxtConfig({
// "vuejs-paginate",
],
devtools: {
enabled: process.env.NODE_ENV === "development",
enabled: true,
vscode: {
reuseExistingServer: true,
},
@ -79,7 +79,7 @@ export default defineNuxtConfig({
features: {
inlineStyles: false,
},
debug: false,
debug: true,
// Modules and plugins
modules: [
"@pinia/nuxt",

View File

@ -1,25 +1,20 @@
import { useAuthStore } from "~/stores/authStore";
import { useStorage } from "@vueuse/core";
// let lsToken = useStorage("token", "GuestAccess");
// if (lsToken == null || lsToken == "" || lsToken == undefined)
// lsToken = "GuestAccess";
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig();
// const { session } = useUserSession();
// const authStore = useAuthStore();
let token = useStorage("id_token", "GuestAccess").value;
const id_token = useCookie("id_token");
const token = id_token.value ?? "GuestAccess";
const baseUrl =
config.public.NUXT_PUBLIC_BASE_URL + config.public.NUXT_PUBLIC_API_NAME;
const api = $fetch.create({
baseURL: baseUrl,
onRequest({ request, options, error }) {
options.baseURL =
config.public.NUXT_PUBLIC_BASE_URL +
config.public.NUXT_PUBLIC_API_NAME +
options.baseURL;
// options.baseURL = options.baseURL;
if (token) {
const headers = (options.headers ||= {});
if (Array.isArray(headers)) {
headers.push(["Authorization", token]);
} else if (headers instanceof Headers) {

View File

@ -2,16 +2,15 @@
import { useStorage } from "@vueuse/core";
export default defineNuxtPlugin((nuxtApp) => {
let token = useStorage("id_token", "GuestAccess").value;
// let token = useStorage("id_token", "GuestAccess").value;
let token = 'GuestAccess';
const config = useRuntimeConfig();
const api = $fetch.create({
onRequest({ request, options, error }) {
options.baseURL =
config.public.NUXT_PUBLIC_BASE_URL +
config.public.NUXT_PUBLIC_API_NAME +
options.baseURL;
baseURL: config.public.NUXT_PUBLIC_BASE_URL +
config.public.NUXT_PUBLIC_API_NAME,
onRequest({ request, options, error }) {
if (token) {
const headers = (options.headers ||= {});
if (Array.isArray(headers)) {
@ -38,7 +37,8 @@ export default defineNuxtPlugin((nuxtApp) => {
// Add custom methods for GET, POST, and DELETE
const http = {
getRequest: (url: string, options = {}) => api(url, { method: "GET", ...options }),
getRequest: (url: string, options = {}) =>
api(url, { method: "GET", ...options }),
postRequest: (url: string, body: any, options = {}) =>
api(url, { method: "POST", body, ...options }),
deleteRequest: (url: string, options = {}) =>

View File

@ -1,9 +0,0 @@
// server/api/login.ts
export default defineEventHandler(async (event) => {
// const body = await readBody(event);
// const response = await $fetch("https://api.example.com/login", {
// method: "POST",
// body,
// });
// return response;
});

View File

@ -1,5 +0,0 @@
// server/api/users/[id].ts
export default defineEventHandler((event) => {
// const id = event.context.params?.id;
// return { userId: id };
});

View File

@ -1,7 +0,0 @@
// server/api/data.ts
export default defineEventHandler(async (event) => {
// const data = await $fetch("https://api.example.com/data", {
// headers: { Authorization: "Bearer my-secret-token" },
// });
// return data;
});

View File

@ -1,7 +0,0 @@
// server/api/data.ts
export default defineEventHandler(async (event) => {
// const data = await $fetch("https://api.example.com/data", {
// headers: { Authorization: "Bearer my-secret-token" },
// });
// return data;
});

View File

@ -1,7 +0,0 @@
// server/api/data.ts
export default defineEventHandler(async (event) => {
// const data = await $fetch("https://api.example.com/data", {
// headers: { Authorization: "Bearer my-secret-token" },
// });
// return data;
});

View File

@ -1,30 +0,0 @@
import { defineEventHandler, readMultipartFormData } from "h3";
import { promises as fs } from "fs";
import sharp from "sharp";
export default defineEventHandler(async (event) => {
const files = await readMultipartFormData(event);
if (!files || files.length === 0) {
return { error: "No files uploaded" };
}
const uploadedFile = files[0];
const fileName = `cropped-${Date.now()}.png`;
const filePath = `./public/uploads/${fileName}`;
// Save the cropped image
await fs.writeFile(filePath, uploadedFile.data);
// Optionally resize the image
const resizedFilePath = `./public/uploads/resized-${fileName}`;
await sharp(uploadedFile.data)
.resize(300, 300) // Adjust dimensions as needed
.toFile(resizedFilePath);
return {
message: "File uploaded successfully",
originalFile: `/uploads/${fileName}`,
resizedFile: `/uploads/resized-${fileName}`,
};
});

View File

@ -1,70 +0,0 @@
const users = [
{
user_id: "583c3ac3f38e84297c002546",
email: "test@test.com",
name: "test@test.com",
given_name: "Hello",
family_name: "Test",
nickname: "test",
last_ip: "94.121.163.63",
logins_count: 15,
created_at: "2016-11-28T14:10:11.338Z",
updated_at: "2016-12-02T01:17:29.310Z",
last_login: "2016-12-02T01:17:29.310Z",
email_verified: true,
},
{
user_id: "583c5484cb79a5fe593425a9",
email: "test1@test.com",
name: "test1@test.com",
given_name: "Hello1",
family_name: "Test1",
nickname: "test1",
last_ip: "94.121.168.53",
logins_count: 1,
created_at: "2016-11-28T16:00:04.209Z",
updated_at: "2016-11-28T16:00:47.203Z",
last_login: "2016-11-28T16:00:47.203Z",
email_verified: true,
},
{
user_id: "583c57672c7686377d2f66c9",
email: "aaa@aaa.com",
name: "aaa@aaa.com",
given_name: "John",
family_name: "Dough",
nickname: "aaa",
last_ip: "94.121.168.53",
logins_count: 2,
created_at: "2016-11-28T16:12:23.777Z",
updated_at: "2016-11-28T16:12:52.353Z",
last_login: "2016-11-28T16:12:52.353Z",
email_verified: true,
},
{
user_id: "5840b954da0529cd293d76fe",
email: "a@a.com",
name: "a@a.com",
given_name: "Jane",
family_name: "Dough",
nickname: "a",
last_ip: "94.121.163.63",
logins_count: 3,
created_at: "2016-12-01T23:59:16.473Z",
updated_at: "2016-12-01T23:59:53.474Z",
last_login: "2016-12-01T23:59:53.474Z",
email_verified: true,
},
{
user_id: "584a9d13e808bcf75f05f580",
email: "test9999@test.com",
given_name: "Dummy",
family_name: "User",
created_at: "2016-12-09T12:01:23.787Z",
updated_at: "2016-12-09T12:01:23.787Z",
email_verified: false,
},
];
export default defineEventHandler((event) => {
return users;
});

@ -1 +1 @@
Subproject commit dc2d5cc460d983ddcb1b302f238070a79d818135
Subproject commit 917824f098aff05dc7ece05c8e159a6bbf1fd35c