mirror of
https://github.com/hpware/news-analyze.git
synced 2025-06-23 21:14:23 +00:00
feat: update environment variables, enhance login functionality with password hashing, and improve UI components for better user experience
This commit is contained in:
parent
26d3998a70
commit
98869d5fce
10 changed files with 105 additions and 29 deletions
|
@ -12,8 +12,7 @@ POSTGRES_URL=
|
|||
|
||||
GROQ_API_KEY=
|
||||
|
||||
NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
|
||||
NUXT_CLERK_SECRET_KEY=
|
||||
PASSWORD_HASH_SALT=""
|
||||
|
||||
# SCRAPING
|
||||
POSTGRES_DB=""
|
||||
|
|
|
@ -28,6 +28,10 @@ App Design: [Freeform](https://www.icloud.com/freeform/026AxB798cViZ9jJ2DkNsXUCQ
|
|||
- Ground.news
|
||||
- 台灣新聞
|
||||
- Threads
|
||||
- xfce's Desktop Interface
|
||||
- juice website
|
||||
- MacOS
|
||||
- Windows XP style X - UI
|
||||
|
||||
## Stack:
|
||||
|
||||
|
|
18
bun.lock
18
bun.lock
|
@ -18,10 +18,12 @@
|
|||
"@uploadthing/nuxt": "^7.1.7",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"argon2": "^0.43.0",
|
||||
"bootstrap-icons": "^1.12.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"emoji-js": "^3.8.1",
|
||||
"groq-sdk": "^0.21.0",
|
||||
"gsap": "^3.13.0",
|
||||
"html-to-json-parser": "^2.0.1",
|
||||
|
@ -460,6 +462,8 @@
|
|||
|
||||
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
|
||||
|
||||
"@phc/format": ["@phc/format@1.0.0", "", {}, "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||
|
@ -812,6 +816,8 @@
|
|||
|
||||
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
|
||||
"argon2": ["argon2@0.43.0", "", { "dependencies": { "@phc/format": "^1.0.0", "node-addon-api": "^8.3.1", "node-gyp-build": "^4.8.4" } }, "sha512-u/HKLcbWShVDhkfwI4hWyiUf3qyX8QhTfaIv2cWE18uqhXCmR5hb6Ed7oqYi2KCQegeAnRhiFzbjzm7i5yl1GA=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"aria-hidden": ["aria-hidden@1.2.4", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A=="],
|
||||
|
@ -1118,6 +1124,10 @@
|
|||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.151", "", {}, "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA=="],
|
||||
|
||||
"emoji-datasource": ["emoji-datasource@15.0.1", "", {}, "sha512-aF5Q6LCKXzJzpG4K0ETiItuzz0xLYxNexR9qWw45/shuuEDWZkOIbeGHA23uopOSYA/LmeZIXIFsySCx+YKg2g=="],
|
||||
|
||||
"emoji-js": ["emoji-js@3.8.1", "", { "dependencies": { "emoji-datasource": "15.0.1" } }, "sha512-yyXMnZLXgqQHAhEm2DKK4Nrca+jbLQfNOP2mLcNTS6XxzzbQLDFHAguPQrtJS4Udot0Pvomwmh1ckQzhrePhKw=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
|
||||
|
||||
"enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="],
|
||||
|
@ -1366,6 +1376,8 @@
|
|||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"idb-keyval": ["idb-keyval@5.1.5", "", { "dependencies": { "safari-14-idb-fix": "^1.0.6" } }, "sha512-J1utxYWQokYjy01LvDQ7WmiAtZCGUSkVi9EIBfUSyLOr/BesnMIxNGASTh9A1LzeISSjSqEPsfFdTss7EE7ofQ=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="],
|
||||
|
@ -1674,7 +1686,7 @@
|
|||
|
||||
"node-abi": ["node-abi@3.75.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="],
|
||||
|
||||
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
|
||||
"node-addon-api": ["node-addon-api@8.3.1", "", {}, "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
|
@ -2018,6 +2030,8 @@
|
|||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"safari-14-idb-fix": ["safari-14-idb-fix@1.0.6", "", {}, "sha512-oTEQOdMwRX+uCtWCKT1nx2gAeSdpr8elg/2gcaKUH00SJU2xWESfkx11nmXwTRHy7xfQoj1o4TTQvdmuBosTnA=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
||||
|
@ -2466,6 +2480,8 @@
|
|||
|
||||
"@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
|
||||
|
||||
"@parcel/watcher/node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
|
||||
|
||||
"@parcel/watcher-wasm/napi-wasm": ["napi-wasm@1.1.3", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="],
|
||||
|
||||
"@poppinss/colors/kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||
|
|
|
@ -13,9 +13,10 @@ const emit = defineEmits(["close"]);
|
|||
|
||||
const isDragging = ref(false);
|
||||
const position = ref({
|
||||
x: props.initialX || Math.floor(window.innerWidth / 2) - Math.random() * 200,
|
||||
y: props.initialY || Math.floor(window.innerHeight / 2) - Math.random() * 10,
|
||||
x: props.initialX || Math.floor(window.innerWidth / 2 - (parseInt(props.width || '400') / 2)),
|
||||
y: props.initialY || Math.floor(window.innerHeight / 2 - (parseInt(props.height || '300') / 2)),
|
||||
});
|
||||
|
||||
const offset = ref({ x: 0, y: 0 });
|
||||
|
||||
const doDrag = useThrottleFn((e: MouseEvent) => {
|
||||
|
|
|
@ -14,7 +14,7 @@ const submitUserPassword = async () => {
|
|||
},
|
||||
body: JSON.stringify({
|
||||
username: userAccount.value,
|
||||
pcssword: password,
|
||||
password: password,
|
||||
}),
|
||||
})
|
||||
const res = await sendData.json();
|
||||
|
@ -45,7 +45,7 @@ const submitUserPassword = async () => {
|
|||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Your Email"
|
||||
placeholder="Username"
|
||||
class="mb-2 p-2 border rounded"
|
||||
v-model="userAccount"
|
||||
/>
|
||||
|
|
|
@ -13,7 +13,7 @@ const { data: source, pending, error } = await useFetch("/api/getData/fetchSourc
|
|||
</script>
|
||||
<template>
|
||||
<div >
|
||||
<div v-for="item in source.data" :key="item.id">
|
||||
<div v-for="item in source?.data" :key="item.id">
|
||||
<h1>{{ item.title }}</h1>
|
||||
<span>{{ item.description }}</span>
|
||||
<a :href="item.url">{{ item.url }}</a>
|
||||
|
|
|
@ -27,10 +27,12 @@
|
|||
"@uploadthing/nuxt": "^7.1.7",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"argon2": "^0.43.0",
|
||||
"bootstrap-icons": "^1.12.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"emoji-js": "^3.8.1",
|
||||
"groq-sdk": "^0.21.0",
|
||||
"gsap": "^3.13.0",
|
||||
"html-to-json-parser": "^2.0.1",
|
||||
|
|
|
@ -123,21 +123,20 @@ const toggleLangMenu = () => {
|
|||
|
||||
// values
|
||||
const activeWindows = ref<associAppWindowInterface>([]);
|
||||
const currentApplication = ref();
|
||||
|
||||
// ?opemapp= component
|
||||
const openApp = ref(false);
|
||||
const openAppId = ref();
|
||||
watch(() => route.query.openapp, (newVal) => {
|
||||
if (newVal) {
|
||||
openApp.value = true;
|
||||
openAppId.value = newVal;
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {},
|
||||
});
|
||||
const openAppNameQuery = ref();
|
||||
|
||||
onMounted(() => {
|
||||
openApp.value = route.query.openapp;
|
||||
openAppId.value = route.query.id;
|
||||
openAppNameQuery.value = route.query.name;
|
||||
if (openApp.value) {
|
||||
openWindow(openApp.value);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const associAppWindow = [
|
||||
{
|
||||
|
@ -149,9 +148,11 @@ const associAppWindow = [
|
|||
height: "500px",
|
||||
},
|
||||
{ name: "login", id: "2", title: t("app.login") , component: LoginWindow },
|
||||
{ name: "sources", id: "3", title: t("app.sources"), component: SourcesWindow },
|
||||
{ name: "sources", id: "3", title: t("app.sources"), component: SourcesWindow }
|
||||
];
|
||||
|
||||
const currentOpenAppId = ref(0);
|
||||
|
||||
const findAndOpenWindow = (windowName: string) => {
|
||||
const app = associAppWindow.find((app) => app.name === windowName)
|
||||
|
||||
|
@ -166,13 +167,14 @@ const findAndOpenWindow = (windowName: string) => {
|
|||
const windowComponent = shallowRef(app.component)
|
||||
|
||||
activeWindows.value.push({
|
||||
id: `${windowName}-${Date.now()}`,
|
||||
id: currentOpenAppId.value,
|
||||
component: windowComponent,
|
||||
name: windowName,
|
||||
title: app.title,
|
||||
width: app.width || "400px",
|
||||
height: app.height || "300px",
|
||||
})
|
||||
currentOpenAppId.value++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,8 +185,18 @@ const closeWindow = (windowId: string) => {
|
|||
console.log("activeWindows.value", activeWindows.value);
|
||||
};
|
||||
|
||||
const topWindow = (windowId: string) => {
|
||||
const windowIndex = activeWindows.value.findIndex(
|
||||
(window) => window.id === windowId,
|
||||
);
|
||||
if (windowIndex !== -1) {
|
||||
const [window] = activeWindows.value.splice(windowIndex, 1);
|
||||
activeWindows.value.push(window);
|
||||
}
|
||||
};
|
||||
|
||||
useSeoMeta({
|
||||
title: currentApplication.value.title + " - " + t("app.title"),
|
||||
title: "hi" + " - Desktop",
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
|
@ -249,9 +261,10 @@ useSeoMeta({
|
|||
</Transition>
|
||||
<!--Main desktop contents-->
|
||||
<div
|
||||
class="flex flex-col justify-center align-center text-center absolute w-full h-screen inset-x-0 inset-y-0 z-[-1]"
|
||||
class="flex flex-col justify-center align-center text-center absolute w-full h-screen inset-x-0 inset-y-0 z-[-10]"
|
||||
id="desktop"
|
||||
></div>
|
||||
<Transition>
|
||||
<div>
|
||||
<DraggableWindow
|
||||
v-for="window in activeWindows"
|
||||
|
@ -260,6 +273,7 @@ useSeoMeta({
|
|||
@close="closeWindow(window.id)"
|
||||
:width="window.width"
|
||||
:height="window.height"
|
||||
@clicked="topWindow(window.id)"
|
||||
>
|
||||
<Suspense>
|
||||
<Component
|
||||
|
@ -269,9 +283,10 @@ useSeoMeta({
|
|||
</Suspense>
|
||||
</DraggableWindow>
|
||||
</div>
|
||||
</Transition>
|
||||
<!--Footer-->
|
||||
<div
|
||||
class="absolute w-[calc(100% - 5px)] inset-x-0 bottom-0 mx-[1.5px] p-3 justify-between align-center flex flex-row"
|
||||
class="absolute w-[calc(100% - 5px)] inset-x-0 bottom-0 mx-[1.5px] p-3 justify-between align-center flex flex-row z-0"
|
||||
>
|
||||
<div class="">
|
||||
<!--Lang-->
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import EmojiConvertor from "emoji-js";
|
||||
import { gsap } from "gsap";
|
||||
import { TextPlugin } from "gsap/TextPlugin";
|
||||
gsap.registerPlugin(TextPlugin);
|
||||
|
@ -15,6 +16,8 @@ const messages = [
|
|||
"BlindSpec",
|
||||
];
|
||||
|
||||
const emoji = new EmojiConvertor();
|
||||
|
||||
onMounted(() => {
|
||||
const tl = gsap.timeline({ repeat: -1 });
|
||||
messages.forEach((message) => {
|
||||
|
@ -76,7 +79,7 @@ onMounted(() => {
|
|||
<div
|
||||
class="flex flex-col justify-center items-center align-middle bg-[#C9C9C9]/60 rounded-xl shadow-lg p-5 m-5 w-[300px] h-[200px]"
|
||||
>
|
||||
<h1 class="text-8xl mt-0">🤔</h1>
|
||||
<h1 class="text-8xl mt-0">{{ emoji.replace_colons(':thinking_face:') }}</h1>
|
||||
<h2 class="text-xl font-bold">Why?</h2>
|
||||
<span class="text-sm"
|
||||
>{{ t("home.whydes")}}</span
|
||||
|
@ -85,7 +88,7 @@ onMounted(() => {
|
|||
<div
|
||||
class="flex flex-col justify-center items-center align-middle bg-[#C9C9C9]/60 rounded-xl shadow-lg p-5 m-5 w-[300px] h-[200px]"
|
||||
>
|
||||
<h1 class="text-8xl mt-0">🧐</h1>
|
||||
<h1 class="text-8xl mt-0">{{ emoji.replace_colons(':face_with_monocle:') }}</h1>
|
||||
<h2 class="text-xl font-bold">How?</h2>
|
||||
<span class="text-sm"
|
||||
>{{ t("home.howdes")}}</span
|
||||
|
|
36
server/api/user/login.ts
Normal file
36
server/api/user/login.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import sql from "~/server/components/postgres";
|
||||
import argon2 from "argon2";
|
||||
export default defineEventHandler(async (event) => {
|
||||
const salt = process.env.PASSWORD_HASH_SALT;
|
||||
if (!salt) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: 'Internal server error'
|
||||
});
|
||||
}
|
||||
const body = await readBody(event);
|
||||
const { username, password } = body;
|
||||
if (!username || !password) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'Username and password are required'
|
||||
});
|
||||
}
|
||||
const USERNAME_PATTERN = /^[a-zA-Z0-9_]{3,20}$/;
|
||||
if (!USERNAME_PATTERN.test(username)) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'Invalid username.'
|
||||
});
|
||||
}
|
||||
// Server side hashing
|
||||
const hashedPassword = await argon2.hash(salt, password);
|
||||
|
||||
// Check if user exists, if not, create a user
|
||||
try {
|
||||
console.log(username);
|
||||
console.log(hashedPassword);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue