mirror of
https://github.com/hpware/news-analyze.git
synced 2025-06-23 13:04:23 +00:00
feat: implement user authentication with GitHub OAuth, create database schema, and enhance navigation
This commit is contained in:
parent
d773473eb0
commit
98ffbec764
12 changed files with 167 additions and 7 deletions
|
@ -1,4 +1,9 @@
|
|||
S3_ACCESS_KEY=""
|
||||
S3_SECRET_KEY=""
|
||||
S3_BUCKETNAME=""
|
||||
S3_ENDPOINT="" # Your S3 server, This can be Cloudflare R2, AWS S3, or just your own Minio infra.
|
||||
S3_ENDPOINT=""
|
||||
|
||||
NUXT_GITHUB_CLIENT_ID=""
|
||||
NUXT_GITHUB_CLIENT_SECRET=""
|
||||
|
||||
POSTGRES_URL=""
|
13
README.md
13
README.md
|
@ -0,0 +1,13 @@
|
|||
# 新聞解析 / News Analyze
|
||||
|
||||
## Stack:
|
||||
- Postgres
|
||||
- Passport.js
|
||||
- Tailwind
|
||||
- Nuxt
|
||||
- Animate.css
|
||||
- GSAP
|
||||
- Zeabur
|
||||
- Minio S3
|
||||
- Nuxt i18N
|
||||
- BunJS
|
15
bun.lock
15
bun.lock
|
@ -19,6 +19,7 @@
|
|||
"bootstrap-icons": "^1.12.1",
|
||||
"gsap": "^3.13.0",
|
||||
"nuxt": "^3.17.2",
|
||||
"passport-github2": "^0.1.12",
|
||||
"prettier": "^3.5.3",
|
||||
"tailwindcss": "3",
|
||||
"tailwindcss-animatecss": "^3.0.5",
|
||||
|
@ -671,6 +672,8 @@
|
|||
|
||||
"base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="],
|
||||
|
||||
"base64url": ["base64url@3.0.1", "", {}, "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="],
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
|
||||
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
|
||||
|
@ -1505,6 +1508,8 @@
|
|||
|
||||
"nypm": ["nypm@0.6.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "pathe": "^2.0.3", "pkg-types": "^2.0.0", "tinyexec": "^0.3.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg=="],
|
||||
|
||||
"oauth": ["oauth@0.10.2", "", {}, "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
|
||||
|
@ -1565,6 +1570,12 @@
|
|||
|
||||
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||
|
||||
"passport-github2": ["passport-github2@0.1.12", "", { "dependencies": { "passport-oauth2": "1.x.x" } }, "sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw=="],
|
||||
|
||||
"passport-oauth2": ["passport-oauth2@1.8.0", "", { "dependencies": { "base64url": "3.x.x", "oauth": "0.10.x", "passport-strategy": "1.x.x", "uid2": "0.0.x", "utils-merge": "1.x.x" } }, "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA=="],
|
||||
|
||||
"passport-strategy": ["passport-strategy@1.0.0", "", {}, "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA=="],
|
||||
|
||||
"path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
|
||||
|
||||
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
||||
|
@ -1965,6 +1976,8 @@
|
|||
|
||||
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
|
||||
|
||||
"uid2": ["uid2@0.0.4", "", {}, "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA=="],
|
||||
|
||||
"ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="],
|
||||
|
||||
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
|
||||
|
@ -2017,6 +2030,8 @@
|
|||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
|
||||
|
||||
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
||||
|
||||
"validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="],
|
||||
|
|
|
@ -25,18 +25,19 @@ const toggleDropdown = () => {
|
|||
</div>
|
||||
<div class="text-[0.9em] left-1/2 absolute transform -translate-x-1/2 space-x-4 items-center">
|
||||
<NuxtLink
|
||||
:to="localePath('home')"
|
||||
:to="localePath('/home')"
|
||||
class="hover:text-blue-500 cursor-pointer transiton-all duration-100"
|
||||
>{{ t("nav.home") }}</NuxtLink
|
||||
>
|
||||
|
||||
<NuxtLink
|
||||
:to="localePath('dailybriefing')"
|
||||
:to="localePath('/dailybriefing')"
|
||||
class="hover:text-blue-500 cursor-pointer transiton-all duration-100"
|
||||
>{{ t("nav.dailybriefing") }}</NuxtLink
|
||||
>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<div class="flex flex-row align-center justify-center text-center">
|
||||
<div class="relative ml-0">
|
||||
<button
|
||||
@click="toggleDropdown"
|
||||
class="flex items-center space-x-1 px-4 py-2 rounded hover:bg-gray-900 transition-all duration-100 mr-5"
|
||||
|
@ -56,7 +57,6 @@ const toggleDropdown = () => {
|
|||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<Transition
|
||||
enter-active-class="animate__animated animate__fadeInDown animate_fastest"
|
||||
leave-active-class="animate__animated animate__fadeOutUp animate_fastest"
|
||||
|
@ -77,6 +77,14 @@ const toggleDropdown = () => {
|
|||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<div class="mr-2 ml-0">
|
||||
<NuxtLink :to="localePath('/system/login')">
|
||||
<button class="text-white hover:text-[#C6C6C6] transition-all duration-150">
|
||||
<i class="bi bi-person text-3xl"></i>
|
||||
</button>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
|
|
50
createDatabase.ts
Normal file
50
createDatabase.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { sql } from "bun";
|
||||
|
||||
const createUsers = await sql`
|
||||
create table if not exists users (
|
||||
uuid text primary key,
|
||||
created_at timestampz default current_timestamp,
|
||||
username text not null unique,
|
||||
oauthProvider text not null,
|
||||
avatarUrl text not null,
|
||||
email text not null,
|
||||
oauthProviderGivenId text not null
|
||||
);
|
||||
`;
|
||||
|
||||
const createNewsProviders = await sql`
|
||||
create table if not exists newsProviders (
|
||||
uuid text primary key,
|
||||
title text not null,
|
||||
slug text unique,
|
||||
website text not null,
|
||||
description text not null,
|
||||
facebookUrl text,
|
||||
twitterUrl text,
|
||||
threadsUrl text,
|
||||
logoUrl text not null,
|
||||
lean text not null
|
||||
)
|
||||
`
|
||||
|
||||
const createAdminPosts = await sql`
|
||||
create table if not exists adminPosts (
|
||||
uuid text primary key,
|
||||
slug text not null unique,
|
||||
content text not null,
|
||||
created_at timestampz default current_timestamp,
|
||||
byUser text not null
|
||||
)
|
||||
`
|
||||
const adminUsers = await sql`
|
||||
create table if not exists adminUsers (
|
||||
uuid text primary key,
|
||||
username text not null unique,
|
||||
passwordHash text not null,
|
||||
created_at timestampz default current_timestamp,
|
||||
lastlogged_at timestampz default current_timestamp,
|
||||
)
|
||||
`
|
||||
|
||||
|
||||
console.log("Creation Complete");
|
7
layouts/admin.vue
Normal file
7
layouts/admin.vue
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
</script>
|
||||
<template>
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
</template>
|
|
@ -8,7 +8,8 @@
|
|||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"prettier": "prettier --write ."
|
||||
"prettier": "prettier --write .",
|
||||
"createdb": "bun run createDatabase.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/noto-sans-tc": "^5.2.5",
|
||||
|
@ -26,6 +27,7 @@
|
|||
"bootstrap-icons": "^1.12.1",
|
||||
"gsap": "^3.13.0",
|
||||
"nuxt": "^3.17.2",
|
||||
"passport-github2": "^0.1.12",
|
||||
"prettier": "^3.5.3",
|
||||
"tailwindcss": "3",
|
||||
"tailwindcss-animatecss": "^3.0.5",
|
||||
|
|
12
pages/admin/login.vue
Normal file
12
pages/admin/login.vue
Normal file
|
@ -0,0 +1,12 @@
|
|||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: "admin"
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex justify-center min-h-screen w-full">
|
||||
<input type="text"/>
|
||||
<input type="password" />
|
||||
<button>登入</button>
|
||||
</div>
|
||||
</template>
|
25
pages/system/login.vue
Normal file
25
pages/system/login.vue
Normal file
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="w-full min-h-screen flex items-center justify-center text-center">
|
||||
<div class="border border-white w-[40%] p-16 justify-center align-center text-center rounded-md backdrop-blur-sm bg-gray-900">
|
||||
<h1 class="text-2xl">Login / Register</h1>
|
||||
<h4 class="text-sm">via OAuth Providers</h4>
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<a href="/api/auth/google">
|
||||
<button class="gap-3 px-10 justify-between align-center text-center bg-gray-500 hover:bg-gray-700 p-2 rounded-md transition-all duration-150">
|
||||
<i class="bi bi-google"></i> <span>Google</span>
|
||||
</button>
|
||||
</a>
|
||||
<a href="/api/auth/github">
|
||||
<button class="gap-3 px-10 bg-gray-500 hover:bg-gray-700 p-2 rounded-md transition-all duration-150">
|
||||
<i class="bi bi-github"></i> <span>Github</span>
|
||||
</button>
|
||||
</a>
|
||||
<a href="/api/auth/discord">
|
||||
<button class="gap-3 px-10 bg-gray-500 hover:bg-gray-700 p-2 rounded-md transition-all duration-150">
|
||||
<i class="bi bi-discord"></i> <span>Discord</span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
8
server/api/auth/callback/github.get.ts
Normal file
8
server/api/auth/callback/github.get.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default defineEventHandler(async (event) => {
|
||||
|
||||
})
|
||||
|
||||
async function findUser(githubUser: any) {
|
||||
console.log("Github User: " + githubUser);
|
||||
|
||||
}
|
16
server/api/auth/github.get.ts
Normal file
16
server/api/auth/github.get.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import crypto from "node:crypto"
|
||||
export default defineEventHandler(async (event) => {
|
||||
const baseUrl = event.node.req.headers.host
|
||||
const protocol = process.env.NODE_ENV === "production" ? "https": "http"
|
||||
const clientId = process.env.NUXT_GITHUB_CLIENT_ID;
|
||||
const callbackUrl = `${protocol}://${baseUrl}/api/auth/github/callback`;
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
setCookie(event, 'oauth_state', state, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
maxAge: 60 * 10,
|
||||
path: '/',
|
||||
})
|
||||
const authorizationUrl = `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(callbackUrl)}&scope=read:user,user:email&state=${state}`
|
||||
await sendRedirect(event, authorizationUrl, 302)
|
||||
})
|
|
@ -14,7 +14,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* noto-sans-tc-chinese-traditional-wght-normal */
|
||||
@font-face {
|
||||
font-family: 'Noto Sans TC Variable';
|
||||
font-style: normal;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue