diff --git a/.env.haditha b/.env.haditha index 0cd8d87..e709690 100644 --- a/.env.haditha +++ b/.env.haditha @@ -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 diff --git a/composables/useApi.ts b/composables/useApi.ts new file mode 100644 index 0000000..4e1fb9b --- /dev/null +++ b/composables/useApi.ts @@ -0,0 +1,11 @@ +import type { UseFetchOptions } from 'nuxt/app' + +export function useApi( + url: string | (() => string), + options?: UseFetchOptions, +) { + return useFetch(url, { + ...options, + $fetch: useNuxtApp().$api as typeof $fetch + }) +} diff --git a/composables/useFetchService.ts b/composables/useFetchService.ts new file mode 100644 index 0000000..de6d065 --- /dev/null +++ b/composables/useFetchService.ts @@ -0,0 +1,81 @@ +import type { FetchOptions } from 'ofetch' +import { useStorage } from '@vueuse/core' + +type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' + +interface HttpRequestOptions 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 ( + method: HttpMethod, + url: string, + options: HttpRequestOptions = {} + ) => { + const headers = { + ...options.headers, + ...(token.value ? { Authorization: token.value } : {}) + } + + try { + const response = await $fetch(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: (url: string, options?: HttpRequestOptions) => + baseFetch('GET', url, options), + + /** + * POST request + */ + postRequest: (url: string, data?: any, options?: HttpRequestOptions) => + baseFetch('POST', url, { ...options, data }), + + /** + * PUT request + */ + putRequest: (url: string, data?: any, options?: HttpRequestOptions) => + baseFetch('PUT', url, { ...options, data }), + + /** + * PATCH request + */ + patchRequest: (url: string, data?: any, options?: HttpRequestOptions) => + baseFetch('PATCH', url, { ...options, data }), + + /** + * DELETE request + */ + deleteRequest: (url: string, options?: HttpRequestOptions) => + baseFetch('DELETE', url, options) + } +} \ No newline at end of file diff --git a/composables/useInfiniteScroll.ts b/composables/useInfiniteScroll.ts new file mode 100644 index 0000000..d79d0fc --- /dev/null +++ b/composables/useInfiniteScroll.ts @@ -0,0 +1,49 @@ +// composables/useInfiniteScroll.ts +export default function useInfiniteScroll( + callback: () => void, + elementId: string +) { + const isFetching = ref(false); + const infiniteScroll = ref(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 }; +} diff --git a/composables/useInfiniteScrollObserver.ts b/composables/useInfiniteScrollObserver.ts new file mode 100644 index 0000000..ed6939d --- /dev/null +++ b/composables/useInfiniteScrollObserver.ts @@ -0,0 +1,46 @@ +// composables/useInfiniteScrollObserver.ts +import { onBeforeUnmount, onMounted, ref } from "vue"; + +export default function useInfiniteScrollObserver(callback) { + const observer = ref(null); + const isFetching = ref(false); + const infiniteScroll = ref(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 }; +} diff --git a/dockerfile b/dockerfile index 3227324..831958e 100644 --- a/dockerfile +++ b/dockerfile @@ -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"] \ No newline at end of file +CMD ["pm2-runtime", ".output/server/index.mjs"] \ No newline at end of file diff --git a/nuxt.config.ts b/nuxt.config.ts index 8e4ec76..452f1f8 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -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", diff --git a/plugins/api.ts b/plugins/api.ts index fe822a1..559a10c 100644 --- a/plugins/api.ts +++ b/plugins/api.ts @@ -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) { diff --git a/plugins/httpService.ts b/plugins/httpService.ts index 880a3b7..156d74e 100644 --- a/plugins/httpService.ts +++ b/plugins/httpService.ts @@ -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({ + baseURL: config.public.NUXT_PUBLIC_BASE_URL + + config.public.NUXT_PUBLIC_API_NAME, + onRequest({ request, options, error }) { - options.baseURL = - config.public.NUXT_PUBLIC_BASE_URL + - config.public.NUXT_PUBLIC_API_NAME + - options.baseURL; - 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 = {}) => diff --git a/server/api/login.ts b/server/api/login.ts deleted file mode 100644 index 81f90a5..0000000 --- a/server/api/login.ts +++ /dev/null @@ -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; -}); diff --git a/server/api/search/[key]/[id].ts b/server/api/search/[key]/[id].ts deleted file mode 100644 index 5d58926..0000000 --- a/server/api/search/[key]/[id].ts +++ /dev/null @@ -1,5 +0,0 @@ -// server/api/users/[id].ts -export default defineEventHandler((event) => { - // const id = event.context.params?.id; - // return { userId: id }; - }); \ No newline at end of file diff --git a/server/api/search/charts.ts b/server/api/search/charts.ts deleted file mode 100644 index 84a25dd..0000000 --- a/server/api/search/charts.ts +++ /dev/null @@ -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; -}); diff --git a/server/api/search/index.ts b/server/api/search/index.ts deleted file mode 100644 index 84a25dd..0000000 --- a/server/api/search/index.ts +++ /dev/null @@ -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; -}); diff --git a/server/api/search/lists.ts b/server/api/search/lists.ts deleted file mode 100644 index 84a25dd..0000000 --- a/server/api/search/lists.ts +++ /dev/null @@ -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; -}); diff --git a/server/api/upload.post.ts b/server/api/upload.post.ts deleted file mode 100644 index 4bae201..0000000 --- a/server/api/upload.post.ts +++ /dev/null @@ -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}`, - }; -}); diff --git a/server/api/users.get.ts b/server/api/users.get.ts deleted file mode 100644 index 35b649a..0000000 --- a/server/api/users.get.ts +++ /dev/null @@ -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; -}); diff --git a/systems/hadith_ui b/systems/hadith_ui index dc2d5cc..917824f 160000 --- a/systems/hadith_ui +++ b/systems/hadith_ui @@ -1 +1 @@ -Subproject commit dc2d5cc460d983ddcb1b302f238070a79d818135 +Subproject commit 917824f098aff05dc7ece05c8e159a6bbf1fd35c