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:
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

View File

@ -81,7 +81,15 @@ const updateContent = async (url: string, tabAction: boolean) => {
const req = await fetch(`/api/home/lt?query=${url.trim()}`);
const data = await req.json();
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;
isDataCached.value = data.cached || false;
displayTranslateContent.value = false;

View File

@ -1,6 +1,11 @@
<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?
import { SparklesIcon, UserIcon, NewspaperIcon } from "lucide-vue-next";
import {
SparklesIcon,
UserIcon,
NewspaperIcon,
StarIcon,
} from "lucide-vue-next";
import translate from "translate";
interface translateInterfaceText {
@ -178,6 +183,7 @@ const aiSummary = async () => {
</div>
</div>
</div>
<button><StarIcon /></button>
</div>
</div>
</template>

View File

@ -1,9 +1,11 @@
<template>
<!--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
width="560"
height="315"
width="600"
height="395"
src="https://www.youtube-nocookie.com/embed/8P3qgVm6m6g?si=0t8eR0wtWv6b3REE"
title="YouTube video player"
frameborder="0"

View File

@ -27,6 +27,10 @@ const userData = ref({
});
const enteruseremail = ref();
onMounted(async () => {
await validateUserInfo();
});
const validateUserInfo = async () => {
const req = await fetch("/api/user/validateUserToken");
const res = await req.json();
if (res.current_spot === "LOGOUT") {
@ -37,7 +41,12 @@ onMounted(async () => {
userData.value = res;
useremail.value = res.email;
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"]);
@ -91,22 +100,24 @@ const checkValidApiKey = () => {
const showDeleteDialog = ref(false);
const showLogoutDialog = ref(false);
const confirmDelete = async () => {
await deleteAccount();
showDeleteDialog.value = false;
await deleteAccount();
await validateUserInfo();
};
const deleteAccount = async () => {
const req = await fetch("/api/user/sendUserChanges", {
method: "DELETE",
});
const res = await res.json();
const res = await req.json();
console.log(res);
};
const submitChangeAction = async (action: string) => {
//const allowedColumns = ["firstname", "email"];
const actions = [
{ name: "NAME", sendValue: enterFirstName.value },
{ name: "USER_EMAIL", sendValue: enteruseremail.value },
{ name: "NAME", SQLSystem: "firstname", sendValue: enterFirstName.value },
{ name: "USER_EMAIL", SQLSystem: "email", sendValue: enteruseremail.value },
];
const actionMatch = actions.find((a) => a.name === action);
@ -121,7 +132,7 @@ const submitChangeAction = async (action: string) => {
"Content-Type": "application/json",
},
body: JSON.stringify({
action: actionMatch.name,
action: actionMatch.SQLSystem,
value: actionMatch.sendValue,
jsonValue: "",
}),
@ -130,7 +141,9 @@ const submitChangeAction = async (action: string) => {
const response = await req.json();
if (response.error) {
console.error("Error updating user data:", response.error);
return;
}
await validateUserInfo();
} catch (error) {
console.error("Failed to submit change:", error);
}
@ -166,6 +179,7 @@ const submitUserPassword = async () => {
success.value = true;
console.log(res);
userAccount.value = "";
await validateUserInfo();
} else {
error.value = true;
errormsg.value = res.error;

View File

@ -2,5 +2,8 @@
const { t } = useI18n();
</script>
<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>

View File

@ -54,9 +54,9 @@ const createSources = await sql`
const createArticlesArchive = await sql`
create table if not exists news_articles (
uuid text primary key,
article_id text primary key,
article_id text,
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") {
return;
}
openWindow(openApp.value);
setTimeout(() => {
openWindow(openApp.value);
}, 2000);
}
});

View File

@ -132,7 +132,14 @@ useSeoMeta({
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<NuxtLink :to="localePath('/desktop')">
<NuxtLink
:to="
localePath({
path: '/desktop',
query: { openapp: 'onboard' },
})
"
>
<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"
>

View File

@ -34,8 +34,8 @@ export default defineEventHandler(async (event) => {
const createUserOtherData = await sql`
create table if not exists user_other_data (
user_id text primary key ,
user text not null unique,
user_id text primary key,
username text not null unique,
groq_api_key text,
starred_news JSON not null,
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 {
createUsers: createUsers,
usersList: usersList,
createUserAiChatHistory: createUserAiChatHistory,
createSources: createSources,
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 CheckKidUnfriendlyContent from "~/components/checks/checkKidUnfriendlyContent";
import * as cheerio from "cheerio";
import { v4 as uuidv4 } from "uuid";
// Caching
@ -91,9 +92,16 @@ export default defineEventHandler(async (event) => {
});
}
});
/*const pushNewsOrg = await sql`
insert into
`*/
const pushNewsOrg = await sql`
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] = {
slug: slug,
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) => {
return {
langPref: "en",
doNotShowLangPrefPopUp: false,
email: "test@yuanhau.com",
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})
`;
const getUserFirstName = await sql`
select * from user_other_data`;
setCookie(event, "token", newToken);
return {
user: fetchUserInfoAgain,

View File

@ -12,23 +12,34 @@ export default defineEventHandler(async (event) => {
const body = await readBody(event);
if (body.jsonValue.length === 0) {
const clearBadDataRegex = /[@-_.+a-zA-Z0-9]{2,}/;
let allowed = true;
if (body.value.match()) {
allowed = false;
}
// Use Static values for now.
const requestChange = "groq_api_key";
const requestChange = body.action || "";
const apiKeyqq = body.value.match(clearBadDataRegex);
const allowedColumns = ["groq_api_key", "another_column_name"];
const allowedColumns = ["firstname", "email"];
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(
`
UPDATE user_other_data SET ${requestChange} = $1
WHERE username = $2`,
`UPDATE user_other_data SET ${requestChange} = $1 WHERE username = $2`,
[apiKeyqq[0], token.user],
);
return {

View File

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