#78 implement auth state change in gui, after login show user

This commit is contained in:
neil 2023-01-03 20:31:59 +08:00
parent c539a2c194
commit aef7eda9f6
17 changed files with 213 additions and 90 deletions

View file

@ -17,7 +17,7 @@ tauri-build = { version = "1.2.0", features = [] }
[dependencies] [dependencies]
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.2.0", features = ["fs-read-dir", "http-all", "shell-all", "window-all"] } tauri = { version = "1.2.0", features = ["fs-create-dir", "fs-read-dir", "fs-read-file", "fs-write-file", "http-all", "path-all", "shell-all", "window-all"] }
uuid = "1.2.1" uuid = "1.2.1"
futures = "0.3" futures = "0.3"

View file

@ -0,0 +1,5 @@
#[tauri::command]
pub fn auth(package: String) {
println!("installing: {}", package);
}

View file

@ -1 +1,2 @@
pub mod packages; pub mod packages;
pub mod auth;

View file

@ -36,6 +36,11 @@
"name": "list-packages", "name": "list-packages",
"cmd": "ls", "cmd": "ls",
"args": ["-R ~/.tea/tea.xyz/var/www | grep 'xz\\|gz'"] "args": ["-R ~/.tea/tea.xyz/var/www | grep 'xz\\|gz'"]
},
{
"name": "open",
"cmd": "open",
"args": ["-a iterm"]
} }
], ],
"sidecar": false "sidecar": false
@ -74,10 +79,15 @@
}, },
"fs": { "fs": {
"readDir": true, "readDir": true,
"createDir": true,
"writeFile": true,
"readFile": true,
"scope": [ "scope": [
"$HOME/.tea/*", "$HOME/.tea/*"
"$APPDATA/*"
] ]
},
"path": {
"all": true
} }
}, },
"bundle": { "bundle": {

View file

@ -1,46 +0,0 @@
<script lang="ts">
import Button from '@tea/ui/Button/Button.svelte';
import { open } from '@tauri-apps/api/shell';
import { getDeviceAuth } from '@api';
const authPage = 'http://localhost:3000/v1'; // https://api.tea.xyz/v1/auth/user?device_id=device_id
let statusMessage = '';
let timer: NodeJS.Timer;
let loop = 0;
const openGithub = () => {
open(authPage);
try {
if (!timer) {
timer = setInterval(pollAuth, 5000)
}
} catch (error) {
console.error(error);
}
};
const pollAuth = async () => {
loop++;
try {
const data = await getDeviceAuth();
console.log('dd:',data);
if (data.status === 'SUCCESS') {
clearInterval(timer);
}
statusMessage = data.status;
} catch (error) {
console.error(error);
}
if (loop > 20 && timer) {
clearInterval(timer);
loop = 0;
}
}
</script>
<p>{loop}:{statusMessage}</p>
<Button onClick={openGithub}>login</Button>

View file

@ -4,6 +4,7 @@
import { searchStore } from '$libs/stores'; import { searchStore } from '$libs/stores';
import SearchInput from '@tea/ui/SearchInput/SearchInput.svelte'; import SearchInput from '@tea/ui/SearchInput/SearchInput.svelte';
import Button from '@tea/ui/Button/Button.svelte'; import Button from '@tea/ui/Button/Button.svelte';
import ProfileNavButton from './ProfileNavButton.svelte';
import { beforeUpdate } from 'svelte'; import { beforeUpdate } from 'svelte';
@ -92,12 +93,7 @@
{/each} {/each}
<footer class="w-full border border-x-0 border-gray"> <footer class="w-full border border-x-0 border-gray">
<a href="/profile"> <ProfileNavButton />
<section class="flex">
<img width="40" height="40" src="/images/bored-ape.png" alt="profile" />
<div class="p-2 text-gray">@user_name</div>
</section>
</a>
</footer> </footer>
</ul> </ul>

View file

@ -0,0 +1,36 @@
<script lang="ts">
import { open } from '@tauri-apps/api/shell';
import { authStore } from '$libs/stores';
import type { User } from '@tea/ui/types';
let user: User | null = null;
const authPage = `http://localhost:3000/v1/auth/user?device_id=${authStore.deviceId}`; // https://api.tea.xyz/v1/auth/user?device_id=device_id
const openGithub = () => {
open(authPage);
try {
authStore.pollSession();
} catch (error) {
console.error(error);
}
};
authStore.subscribe((u) => (user = u));
</script>
{#if user}
<a href="/profile">
<section class="flex">
<img width="40" height="40" src={user.avatar_url || '/images/bored-ape.png'} alt="profile" />
<div class="p-2 text-gray">@{user.login}</div>
</section>
</a>
{:else}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<section class="flex" on:click={openGithub}>
<figure class="p-2">
<img width="28" height="28" src="/images/github.png" alt="profile" />
</figure>
<div class="p-2 text-gray">Login</div>
</section>
{/if}

View file

@ -1,23 +1,36 @@
<script lang="ts"> <script lang="ts">
import '$appcss'; import '$appcss';
import { authStore } from '$libs/stores';
import type { User } from '@tea/ui/types';
let user: User | null = null;
const authPage = `http://localhost:3000/v1/auth/user?device_id=${authStore.deviceId}`; // https://api.tea.xyz/v1/auth/user?device_id=device_id
authStore.subscribe((u) => (user = u));
</script> </script>
<section class="border-2 border-gray bg-black p-2"> {#if user}
<section class="border-2 border-gray bg-black p-2">
<div class="profile_banner container flex border border-gray bg-black"> <div class="profile_banner container flex border border-gray bg-black">
<img class="w-1/5" src="/images/bored-ape.png" alt="profile" /> <img class="w-1/5" src={user.avatar_url || '/images/bored-ape.png'} alt="profile" />
<div class="flex w-4/5 items-center p-5"> <div class="flex w-4/5 items-center p-5">
<div class="w-1/2 pl-5"> <div class="w-1/2 pl-5">
<p class="uppercase text-gray">Authenticated with GitHub</p> <p class="uppercase text-gray">Authenticated with GitHub</p>
<p /> <p />
<p class="text-4xl text-primary">@Username</p> <p class="text-4xl text-primary">@{user.login}</p>
</div> </div>
<div class="h-full border-l border-gray" /> <div class="h-full border-l border-gray" />
<div class="w-1/2 pl-10"> <div class="w-1/2 pl-10">
<p class="uppercase leading-loose text-gray"> <p class="uppercase leading-loose text-gray">
Country: <span>Germany</span><br />Wallet: Country: <span>{user?.country}</span><br />Wallet:
{#if user.wallet}
<span>{user.wallet}</span>
{:else}
<a class="text-green underline" href="/">Connect Now</a> <a class="text-green underline" href="/">Connect Now</a>
{/if}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
{/if}

View file

@ -328,6 +328,16 @@ export async function getDeviceAuth(): Promise<any> {
// const data = await get<any>(`/auth/device/${deviceId}`); // const data = await get<any>(`/auth/device/${deviceId}`);
return { return {
status: 'SUCCESS', status: 'SUCCESS',
user: {}, user: {
developer_id: 'xxx',
name: 'Neil paul Molina',
login: 'getneil',
avatar_url: 'https://avatars.githubusercontent.com/u/7913978?v=4',
created_at: 'xxx',
updated_at: 'xxx',
country: 'germany',
wallet: 'wallet'
},
key: 'xxx'
}; };
} }

View file

@ -19,8 +19,8 @@ import type { GUIPackage, Course, Category, AuthStatus } from '../types';
import * as mock from './mock'; import * as mock from './mock';
import { PackageStates } from '../types'; import { PackageStates } from '../types';
const base = 'https://api.tea.xyz/v1'; // const base = 'https://api.tea.xyz/v1';
// const base = 'http://localhost:3000/v1'; const base = 'http://localhost:3000/v1';
async function get<T>(path: string, query?: { [key: string]: string }) { async function get<T>(path: string, query?: { [key: string]: string }) {
console.log('path', path); console.log('path', path);
@ -29,8 +29,8 @@ async function get<T>(path: string, query?: { [key: string]: string }) {
console.log('uri:', uri); console.log('uri:', uri);
const { data } = await client.get<T>(uri.toString(), { const { data } = await client.get<T>(uri.toString(), {
headers: { headers: {
Authorization: 'public', // TODO: figure out why req w/o Authorization does not work Authorization: 'public' // TODO: figure out why req w/o Authorization does not work
'cache-control': 'no-cache' // 'cache-control': 'no-cache'
}, },
query: query || {} query: query || {}
}); });
@ -170,7 +170,8 @@ export async function getCategorizedPackages(): Promise<Category[]> {
type DeviceAuth = { type DeviceAuth = {
status: AuthStatus; status: AuthStatus;
user: User; user: User;
} key: string;
};
export async function getDeviceAuth(): Promise<DeviceAuth> { export async function getDeviceAuth(): Promise<DeviceAuth> {
const deviceId = 'xyxz123'; const deviceId = 'xyxz123';

View file

@ -6,6 +6,7 @@ import type { GUIPackage } from '$libs/types';
// TODO: figure out a better structure for managing states maybe turn them into separate files? // TODO: figure out a better structure for managing states maybe turn them into separate files?
import { getPackages, getFeaturedPackages, getPackageReviews, getAllPosts } from '@api'; import { getPackages, getFeaturedPackages, getPackageReviews, getAllPosts } from '@api';
import initAuthStore from './stores/auth';
export const backLink = writable<string>('/'); export const backLink = writable<string>('/');
@ -170,3 +171,5 @@ function initSearchStore() {
} }
export const searchStore = initSearchStore(); export const searchStore = initSearchStore();
export const authStore = initAuthStore();

View file

@ -0,0 +1,95 @@
import { writable } from 'svelte/store';
import { BaseDirectory, createDir, readTextFile, writeTextFile } from '@tauri-apps/api/fs';
import { join } from '@tauri-apps/api/path';
import { getDeviceAuth } from '@api';
import type { User } from '@tea/ui/types';
const basePath = '.tea/tea.xyz/gui';
interface Session {
key: string;
user: any;
}
export default function initAuthStore() {
const deviceId = 'abcdevf'; // ideally randomly generated on install
const session = writable<Session>();
let pollLoop = 0;
initSession();
let timer: NodeJS.Timer | null;
// TODO:
// fetch session data from local
// fetch session data remotely
// update local session data
async function pollSession() {
if (!timer) {
timer = setInterval(async () => {
pollLoop++;
try {
const data = await getDeviceAuth();
if (data.status === 'SUCCESS') {
session.set({
key: data.key,
user: data.user
});
timer && clearInterval(timer);
timer = null;
}
console.log(data);
} catch (error) {
console.error(error);
}
if (pollLoop > 20 && timer) {
clearInterval(timer);
pollLoop = 0;
timer = null;
}
}, 2000);
}
}
return {
deviceId,
subscribe: (cb: (u: User) => void) => {
return session.subscribe((v) => v && cb(v.user));
},
pollSession
};
}
const initSession = async (): Promise<Session | void> => {
await createGuiDataFolder();
const session = await getSessionData();
console.log(session);
};
const createGuiDataFolder = async () => {
await createDir(basePath, {
dir: BaseDirectory.Home,
recursive: true
});
};
const getSessionData = async (): Promise<Session | void> => {
const sessionFilePath = await join(basePath, 'tmp.dat');
try {
const data = await readTextFile(sessionFilePath, {
dir: BaseDirectory.Home
});
// TODO: decrypt then return
console.log('data:', data);
} catch (error) {
console.error(error);
await writeTextFile(sessionFilePath, '', {
dir: BaseDirectory.Home
});
}
console.log(sessionFilePath);
};
const saveSessionData = async (data: { [key: string]: string | number | Date }) => {
const sessionFilePath = await join(basePath, 'tmp.dat');
// TODO: encrypt and write
};

View file

@ -34,5 +34,5 @@ export enum AuthStatus {
UNKNOWN = 'UNKNOWN', UNKNOWN = 'UNKNOWN',
PENDING = 'PENDING', PENDING = 'PENDING',
SUCCESS = 'SUCCESS', SUCCESS = 'SUCCESS',
FAILED = 'FAILED', FAILED = 'FAILED'
} }

View file

@ -9,7 +9,7 @@
import News from '$components/News/News.svelte'; import News from '$components/News/News.svelte';
import CategorizedPackages from '$components/CategorizedPackages/CategorizedPackages.svelte'; import CategorizedPackages from '$components/CategorizedPackages/CategorizedPackages.svelte';
backLink.set(''); backLink.set('');
console.log("test", window.location) console.log('test', window.location);
</script> </script>
<div> <div>

View file

@ -6,15 +6,11 @@
import Badges from '$components/Badges/Badges.svelte'; import Badges from '$components/Badges/Badges.svelte';
import InstalledPackages from '$components/InstalledPackages/InstalledPackages.svelte'; import InstalledPackages from '$components/InstalledPackages/InstalledPackages.svelte';
import { backLink } from '$libs/stores'; import { backLink } from '$libs/stores';
import Auth from '$components/Auth/Auth.svelte';
backLink.set('/'); backLink.set('/');
</script> </script>
<div> <div>
<PageHeader>PROFILE</PageHeader> <PageHeader>PROFILE</PageHeader>
<section>
<Auth/>
</section>
<section> <section>
<ProfileBanner /> <ProfileBanner />
</section> </section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View file

@ -36,7 +36,10 @@ export type AirtablePost = {
}; };
export type User = { export type User = {
username: string; developer_id: string;
avatar_url?: string;
name: string;
login: string;
country?: string; country?: string;
eth_wallet_address?: string; wallet?: string;
} };