Merge pull request #6 from hpware/beta

Merge Beta PR
This commit is contained in:
元皓 2025-06-15 07:27:42 +08:00 committed by GitHub
commit a33a2364ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 226 additions and 36 deletions

1
.github/funding.yml vendored Normal file
View File

@ -0,0 +1 @@
ko_fi: howard00

View File

@ -19,7 +19,7 @@ Video Guide: [YouTube](https://youtu.be/8P3qgVm6m6g)
## Demo: ## Demo:
Production (Latest Docker Image): https://yhw.tw/news Production (Latest Docker Image): https://yhw.tw/news
Beta (Beta Docekr Image): https://newsbeta.20090526.xyz Beta (Beta Docker Image): https://newsbeta.20090526.xyz
## Video Guide ## Video Guide

View File

@ -81,7 +81,15 @@ const updateContent = async (url: string, tabAction: boolean) => {
const req = await fetch(`/api/home/lt?query=${url.trim()}`); const req = await fetch(`/api/home/lt?query=${url.trim()}`);
const data = await req.json(); const data = await req.json();
if (data) { if (data) {
contentArray.value = [...data.uuidData, ...(data.nuuiddata?.items || [])]; // Made by coderabbit: https://github.com/hpware/news-analyze/pull/6#discussion_r2144713017
const coolArray = [
...(data.uuidData ?? []),
...(data.nuuiddata?.items ?? []),
];
contentArray.value =
coolArray.sort(
(title1, title2) => title2.publishTimeUnix - title1.publishTimeUnix,
) || [];
switchTabs.value = false; switchTabs.value = false;
isDataCached.value = data.cached || false; isDataCached.value = data.cached || false;
displayTranslateContent.value = false; displayTranslateContent.value = false;

View File

@ -1,6 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
// FOR THIS MODULE DO NOT USE THE ?APPNAME URL TYPE, IT WILL FALL AT ALL TIMES, I HAVE NO CLUE WHY IS BEHAVIOR HAPPENING RN? // FOR THIS MODULE DO NOT USE THE ?APPNAME URL TYPE, IT WILL FALL AT ALL TIMES, I HAVE NO CLUE WHY IS BEHAVIOR HAPPENING RN?
import { SparklesIcon, UserIcon, NewspaperIcon } from "lucide-vue-next"; import {
SparklesIcon,
UserIcon,
NewspaperIcon,
StarIcon,
} from "lucide-vue-next";
import translate from "translate"; import translate from "translate";
interface translateInterfaceText { interface translateInterfaceText {
@ -178,6 +183,7 @@ const aiSummary = async () => {
</div> </div>
</div> </div>
</div> </div>
<button><StarIcon /></button>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,9 +1,11 @@
<template> <template>
<!--YouTube Embed--> <!--YouTube Embed-->
<div class="justify-center absolute inset-0 flex flex-col"> <div
class="justify-center align-center text-center absolute inset-0 flex flex-col mt-12"
>
<iframe <iframe
width="560" width="600"
height="315" height="395"
src="https://www.youtube-nocookie.com/embed/8P3qgVm6m6g?si=0t8eR0wtWv6b3REE" src="https://www.youtube-nocookie.com/embed/8P3qgVm6m6g?si=0t8eR0wtWv6b3REE"
title="YouTube video player" title="YouTube video player"
frameborder="0" frameborder="0"

View File

@ -27,6 +27,10 @@ const userData = ref({
}); });
const enteruseremail = ref(); const enteruseremail = ref();
onMounted(async () => { onMounted(async () => {
await validateUserInfo();
});
const validateUserInfo = async () => {
const req = await fetch("/api/user/validateUserToken"); const req = await fetch("/api/user/validateUserToken");
const res = await req.json(); const res = await req.json();
if (res.current_spot === "LOGOUT") { if (res.current_spot === "LOGOUT") {
@ -37,7 +41,12 @@ onMounted(async () => {
userData.value = res; userData.value = res;
useremail.value = res.email; useremail.value = res.email;
isLoggedIn.value = true; isLoggedIn.value = true;
}); };
const intervalTime = 1000 * 60 * 2; // Validate user Info for every ten min while the admin page is opened.
setInterval(async () => {
await validateUserInfo();
}, intervalTime);
const emit = defineEmits(["windowopener"]); const emit = defineEmits(["windowopener"]);
@ -91,22 +100,24 @@ const checkValidApiKey = () => {
const showDeleteDialog = ref(false); const showDeleteDialog = ref(false);
const showLogoutDialog = ref(false); const showLogoutDialog = ref(false);
const confirmDelete = async () => { const confirmDelete = async () => {
await deleteAccount();
showDeleteDialog.value = false; showDeleteDialog.value = false;
await deleteAccount();
await validateUserInfo();
}; };
const deleteAccount = async () => { const deleteAccount = async () => {
const req = await fetch("/api/user/sendUserChanges", { const req = await fetch("/api/user/sendUserChanges", {
method: "DELETE", method: "DELETE",
}); });
const res = await res.json(); const res = await req.json();
console.log(res); console.log(res);
}; };
const submitChangeAction = async (action: string) => { const submitChangeAction = async (action: string) => {
//const allowedColumns = ["firstname", "email"];
const actions = [ const actions = [
{ name: "NAME", sendValue: enterFirstName.value }, { name: "NAME", SQLSystem: "firstname", sendValue: enterFirstName.value },
{ name: "USER_EMAIL", sendValue: enteruseremail.value }, { name: "USER_EMAIL", SQLSystem: "email", sendValue: enteruseremail.value },
]; ];
const actionMatch = actions.find((a) => a.name === action); const actionMatch = actions.find((a) => a.name === action);
@ -121,7 +132,7 @@ const submitChangeAction = async (action: string) => {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
action: actionMatch.name, action: actionMatch.SQLSystem,
value: actionMatch.sendValue, value: actionMatch.sendValue,
jsonValue: "", jsonValue: "",
}), }),
@ -130,7 +141,9 @@ const submitChangeAction = async (action: string) => {
const response = await req.json(); const response = await req.json();
if (response.error) { if (response.error) {
console.error("Error updating user data:", response.error); console.error("Error updating user data:", response.error);
return;
} }
await validateUserInfo();
} catch (error) { } catch (error) {
console.error("Failed to submit change:", error); console.error("Failed to submit change:", error);
} }
@ -166,6 +179,7 @@ const submitUserPassword = async () => {
success.value = true; success.value = true;
console.log(res); console.log(res);
userAccount.value = ""; userAccount.value = "";
await validateUserInfo();
} else { } else {
error.value = true; error.value = true;
errormsg.value = res.error; errormsg.value = res.error;

View File

@ -2,5 +2,8 @@
const { t } = useI18n(); const { t } = useI18n();
</script> </script>
<template> <template>
<div></div> <div class="justify-center align-center text-center">
<h1 class="text-2xl text-bold">{{ t("pages.tos.title") }}</h1>
<p>{{ t("pages.tos.content") }}</p>
</div>
</template> </template>

View File

@ -54,9 +54,9 @@ const createSources = await sql`
const createArticlesArchive = await sql` const createArticlesArchive = await sql`
create table if not exists news_articles ( create table if not exists news_articles (
uuid text primary key, uuid text primary key,
article_id text primary key, article_id text,
jsondata json not null, jsondata json not null,
archive_timestamp timestamp default CURRENT_TIMESTAMP, archive_timestamp timestamp default CURRENT_TIMESTAMP
) )
`; `;

View File

@ -374,7 +374,9 @@ onMounted(async () => {
if (openApp.value === "newsView") { if (openApp.value === "newsView") {
return; return;
} }
setTimeout(() => {
openWindow(openApp.value); openWindow(openApp.value);
}, 2000);
} }
}); });

View File

@ -132,7 +132,14 @@ useSeoMeta({
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<NuxtLink :to="localePath('/desktop')"> <NuxtLink
:to="
localePath({
path: '/desktop',
query: { openapp: 'onboard' },
})
"
>
<button <button
class="m-4 mr-1 ml-1 bg-[#8C9393] text-white p-3 rounded-[10px] bg-gradient-to-l from-sky-500 to-purple-600 transition-all duration-150 hover:transform hover:scale-105 hover:shadow-lg" class="m-4 mr-1 ml-1 bg-[#8C9393] text-white p-3 rounded-[10px] bg-gradient-to-l from-sky-500 to-purple-600 transition-all duration-150 hover:transform hover:scale-105 hover:shadow-lg"
> >

View File

@ -35,7 +35,7 @@ export default defineEventHandler(async (event) => {
const createUserOtherData = await sql` const createUserOtherData = await sql`
create table if not exists user_other_data ( create table if not exists user_other_data (
user_id text primary key, user_id text primary key,
user text not null unique, username text not null unique,
groq_api_key text, groq_api_key text,
starred_news JSON not null, starred_news JSON not null,
translate_provider text, translate_provider text,
@ -51,11 +51,21 @@ export default defineEventHandler(async (event) => {
) )
`; `;
const createArticlesArchive = await sql`
create table if not exists news_articles (
uuid text primary key,
article_id text primary key,
jsondata json not null,
archive_timestamp timestamp default CURRENT_TIMESTAMP,
)
`;
return { return {
createUsers: createUsers, createUsers: createUsers,
usersList: usersList, usersList: usersList,
createUserAiChatHistory: createUserAiChatHistory, createUserAiChatHistory: createUserAiChatHistory,
createSources: createSources, createSources: createSources,
createUserOtherData: createUserOtherData, createUserOtherData: createUserOtherData,
createArticlesArchive: createArticlesArchive,
}; };
}); });

View File

@ -0,0 +1,11 @@
import sql from "~/server/components/postgres";
export default defineEventHandler(async (event) => {
const articles = await sql`
SELECT * FROM news_articles;
`;
setHeaders(event, {
"Content-Type": "application/json",
"Content-Disposition": `attachment; filename="news-articles-export-${new Date().toISOString().split("T")[0]}.json"`,
});
return articles;
});

View File

@ -1,6 +1,7 @@
import sql from "~/server/components/postgres"; import sql from "~/server/components/postgres";
import CheckKidUnfriendlyContent from "~/components/checks/checkKidUnfriendlyContent"; import CheckKidUnfriendlyContent from "~/components/checks/checkKidUnfriendlyContent";
import * as cheerio from "cheerio"; import * as cheerio from "cheerio";
import { v4 as uuidv4 } from "uuid";
// Caching // Caching
@ -91,9 +92,16 @@ export default defineEventHandler(async (event) => {
}); });
} }
}); });
/*const pushNewsOrg = await sql` const pushNewsOrg = await sql`
insert into insert into lt_news_org (news_id, name, description)
`*/ values (${uuidv4()}, ${newsOrgName}, ${description})
`;
console.log(pushNewsOrg);
/**
* news_id text primary key,
name text not null,
description text
*/
cache[slug] = { cache[slug] = {
slug: slug, slug: slug,
title: newsOrgName, title: newsOrgName,

View File

@ -0,0 +1,30 @@
import sql from "~/server/components/postgres";
import getUserTokenMinusSQLInjection from "~/server/components/getUserToken";
export default defineEventHandler(async (event) => {
try {
const slug = getRouterParam(event, "slug");
const token = await getUserTokenMinusSQLInjection(event);
if (token.error.length !== 0) {
return {
error: token.error,
};
}
const getOtherUserDataJsonFile = await sql`
SELECT starred_news from user_other_data
where username = ${token.user}
`;
if (getOtherUserDataJsonFile.length === 0) {
return {
error: "ERR_NO_DATA",
};
}
const jsonData = getOtherUserDataJsonFile[0].starred_news;
return jsonData;
} catch (e) {
console.log(e);
return {
error: "INTERNAL_SERVER_ERR",
e: e.message,
};
}
});

View File

@ -0,0 +1,30 @@
import sql from "~/server/components/postgres";
import getUserTokenMinusSQLInjection from "~/server/components/getUserToken";
export default defineEventHandler(async (event) => {
try {
const slug = getRouterParam(event, "slug");
const token = await getUserTokenMinusSQLInjection(event);
if (token.error.length !== 0) {
return {
error: token.error,
};
}
const getOtherUserDataJsonFile = await sql`
SELECT starred_news from user_other_data
where username = ${token.user}
`;
if (getOtherUserDataJsonFile.length === 0) {
return {
error: "ERR_NO_DATA",
};
}
const jsonData = getOtherUserDataJsonFile[0].starred_news;
return jsonData;
} catch (e) {
console.log(e);
return {
error: "INTERNAL_SERVER_ERR",
e: e.message,
};
}
});

View File

@ -1,7 +1,6 @@
// Fixed data for testing /*// Fixed data for testing
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
return { return {
langPref: "en",
doNotShowLangPrefPopUp: false, doNotShowLangPrefPopUp: false,
email: "test@yuanhau.com", email: "test@yuanhau.com",
name: "Howard", name: "Howard",
@ -13,3 +12,48 @@ export default defineEventHandler(async (event) => {
}, },
}; };
}); });
*/
import sql from "~/server/components/postgres";
import getUserTokenMinusSQLInjection from "~/server/components/getUserToken";
export default defineEventHandler(async (event) => {
try {
const token = await getUserTokenMinusSQLInjection(event);
if (token.error.length !== 0) {
return {
error: token.error,
};
}
const fetchMainData = await sql`
SELECT * FROM users
WHERE username = ${token.user}
`;
const fetchOtherUserData = await sql`
SELECT * FROM user_other_data
WHERE username = ${token.user}
`;
if (fetchMainData.length === 0 || fetchOtherUserData.length === 0) {
return {
error: "ERR_USER_DOESNT_EXIST",
};
}
return {
doNotShowLangPrefPopUp:
fetchOtherUserData[0].remove_translate_popup || false,
email: fetchMainData[0].email || "",
name: fetchMainData[0].firstname || "",
useCustomGroqKey: +(fetchOtherUserData[0].groq_api_key?.length ?? 0) > 0,
translate: {
enabled: fetchOtherUserData[0].translate_enabled || false,
lang: "en",
provider: fetchOtherUserData[0].translate_provider || "google",
},
};
} catch (e) {
console.log(e);
return {
error: "ERR_SERVER_SIDE",
e: e.message,
};
}
});

View File

@ -81,6 +81,9 @@ export default defineEventHandler(async (event) => {
VALUES (${fetchUserInfoAgain[0].username}, ${newToken}) VALUES (${fetchUserInfoAgain[0].username}, ${newToken})
`; `;
const getUserFirstName = await sql`
select * from user_other_data`;
setCookie(event, "token", newToken); setCookie(event, "token", newToken);
return { return {
user: fetchUserInfoAgain, user: fetchUserInfoAgain,

View File

@ -12,23 +12,34 @@ export default defineEventHandler(async (event) => {
const body = await readBody(event); const body = await readBody(event);
if (body.jsonValue.length === 0) { if (body.jsonValue.length === 0) {
const clearBadDataRegex = /[@-_.+a-zA-Z0-9]{2,}/; const clearBadDataRegex = /[@-_.+a-zA-Z0-9]{2,}/;
let allowed = true;
if (body.value.match()) {
allowed = false;
}
// Use Static values for now. // Use Static values for now.
const requestChange = "groq_api_key"; const requestChange = body.action || "";
const apiKeyqq = body.value.match(clearBadDataRegex); const apiKeyqq = body.value.match(clearBadDataRegex);
const allowedColumns = ["groq_api_key", "another_column_name"]; const allowedColumns = ["firstname", "email"];
if (!allowedColumns.includes(requestChange)) { if (!allowedColumns.includes(requestChange)) {
throw new Error("Invalid column name provided"); return {
error: "ERR_NOT_ALLOWED",
};
} else if (requestChange === "firstname") {
const sqlC = await sql`
UPDATE users SET firstname = ${apiKeyqq[0]}
WHERE username = ${token.user}`;
return {
sqlC: sqlC,
success: true,
};
} else if (requestChange === "email") {
const sqlC = await sql`
UPDATE users SET email = ${apiKeyqq[0]}
WHERE username = ${token.user}`;
return {
sqlC: sqlC,
success: true,
};
} }
const sqlC = await sql.unsafe( const sqlC = await sql.unsafe(
` `UPDATE user_other_data SET ${requestChange} = $1 WHERE username = $2`,
UPDATE user_other_data SET ${requestChange} = $1
WHERE username = $2`,
[apiKeyqq[0], token.user], [apiKeyqq[0], token.user],
); );
return { return {

View File

@ -43,7 +43,7 @@ export default defineEventHandler(async (event) => {
} }
return { return {
userAccount: fetchViaSQL[0].username, userAccount: fetchViaSQL[0].username,
firstName: fetchViaSQL[0].firstName, firstName: "",
requested_action: "CONTINUE", requested_action: "CONTINUE",
current_spot: "KEEP_LOGIN", current_spot: "KEEP_LOGIN",
email: fetchViaSQL[0].email, email: fetchViaSQL[0].email,