mirror of
https://github.com/ivabus/gui
synced 2025-06-07 15:50:27 +03:00
#78 implement auth state change in gui, after login show user
This commit is contained in:
parent
c539a2c194
commit
aef7eda9f6
17 changed files with 213 additions and 90 deletions
|
@ -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"
|
||||||
|
|
||||||
|
|
5
modules/gui/src-tauri/src/handlers/auth.rs
Normal file
5
modules/gui/src-tauri/src/handlers/auth.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn auth(package: String) {
|
||||||
|
println!("installing: {}", package);
|
||||||
|
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
pub mod packages;
|
pub mod packages;
|
||||||
|
pub mod auth;
|
|
@ -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": {
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
36
modules/gui/src/components/NavBar/ProfileNavButton.svelte
Normal file
36
modules/gui/src/components/NavBar/ProfileNavButton.svelte
Normal 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}
|
|
@ -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}
|
||||||
<div class="profile_banner container flex border border-gray bg-black">
|
<section class="border-2 border-gray bg-black p-2">
|
||||||
<img class="w-1/5" src="/images/bored-ape.png" alt="profile" />
|
<div class="profile_banner container flex border border-gray bg-black">
|
||||||
<div class="flex w-4/5 items-center p-5">
|
<img class="w-1/5" src={user.avatar_url || '/images/bored-ape.png'} alt="profile" />
|
||||||
<div class="w-1/2 pl-5">
|
<div class="flex w-4/5 items-center p-5">
|
||||||
<p class="uppercase text-gray">Authenticated with GitHub</p>
|
<div class="w-1/2 pl-5">
|
||||||
<p />
|
<p class="uppercase text-gray">Authenticated with GitHub</p>
|
||||||
<p class="text-4xl text-primary">@Username</p>
|
<p />
|
||||||
</div>
|
<p class="text-4xl text-primary">@{user.login}</p>
|
||||||
<div class="h-full border-l border-gray" />
|
</div>
|
||||||
<div class="w-1/2 pl-10">
|
<div class="h-full border-l border-gray" />
|
||||||
<p class="uppercase leading-loose text-gray">
|
<div class="w-1/2 pl-10">
|
||||||
Country: <span>Germany</span><br />Wallet:
|
<p class="uppercase leading-loose text-gray">
|
||||||
<a class="text-green underline" href="/">Connect Now</a>
|
Country: <span>{user?.country}</span><br />Wallet:
|
||||||
</p>
|
{#if user.wallet}
|
||||||
|
<span>{user.wallet}</span>
|
||||||
|
{:else}
|
||||||
|
<a class="text-green underline" href="/">Connect Now</a>
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
{/if}
|
||||||
|
|
|
@ -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'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,10 +170,11 @@ 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';
|
||||||
const data = await get<DeviceAuth>(`/auth/device/${deviceId}`);
|
const data = await get<DeviceAuth>(`/auth/device/${deviceId}`);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
95
modules/gui/src/libs/stores/auth.ts
Normal file
95
modules/gui/src/libs/stores/auth.ts
Normal 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
|
||||||
|
};
|
|
@ -34,5 +34,5 @@ export enum AuthStatus {
|
||||||
UNKNOWN = 'UNKNOWN',
|
UNKNOWN = 'UNKNOWN',
|
||||||
PENDING = 'PENDING',
|
PENDING = 'PENDING',
|
||||||
SUCCESS = 'SUCCESS',
|
SUCCESS = 'SUCCESS',
|
||||||
FAILED = 'FAILED',
|
FAILED = 'FAILED'
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
BIN
modules/gui/static/images/github.png
Normal file
BIN
modules/gui/static/images/github.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
|
@ -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;
|
||||||
}
|
};
|
||||||
|
|
Loading…
Reference in a new issue