mirror of
https://github.com/ivabus/gui
synced 2025-06-07 15:50:27 +03:00
Merge pull request #142 from teaxyz/refactor-tauri-api
#136 refactor api integration
This commit is contained in:
commit
3b80e8f431
9 changed files with 111 additions and 110 deletions
|
@ -43,12 +43,14 @@
|
|||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/url-join": "^4.0.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"buffer": "^6.0.3",
|
||||
"fuse.js": "^6.6.2",
|
||||
"lodash": "^4.17.21",
|
||||
"lorem-ipsum": "^2.0.8",
|
||||
"svelte-watch-resize": "^1.0.3"
|
||||
"svelte-watch-resize": "^1.0.3",
|
||||
"url-join": "^5.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
import { open } from '@tauri-apps/api/shell';
|
||||
import { authStore } from '$libs/stores';
|
||||
import type { Developer } from '@tea/ui/types';
|
||||
import { apiBaseUrl } from '@api';
|
||||
import { baseUrl } from '$libs/v1Client';
|
||||
|
||||
let user: Developer | null = null;
|
||||
const deviceId = authStore.deviceIdStore;
|
||||
|
||||
const openGithub = () => {
|
||||
open(`${apiBaseUrl}/auth/user?device_id=${$deviceId}`);
|
||||
open(`${baseUrl}/auth/user?device_id=${$deviceId}`);
|
||||
try {
|
||||
authStore.pollSession();
|
||||
} catch (error) {
|
||||
|
|
|
@ -11,8 +11,6 @@ import { PackageStates } from '../types';
|
|||
import { loremIpsum } from 'lorem-ipsum';
|
||||
import _ from 'lodash';
|
||||
|
||||
export const apiBaseUrl = 'https://api.tea.xyz/v1';
|
||||
|
||||
const packages: Package[] = [
|
||||
{
|
||||
slug: 'mesonbuild_com',
|
||||
|
|
|
@ -11,73 +11,22 @@
|
|||
* - connect to a local platform api and returns a data
|
||||
*/
|
||||
import { getClient } from '@tauri-apps/api/http';
|
||||
// import { invoke } from '@tauri-apps/api';
|
||||
import { Command } from '@tauri-apps/api/shell';
|
||||
import { readDir, BaseDirectory } from '@tauri-apps/api/fs';
|
||||
|
||||
import urlJoin from 'url-join';
|
||||
|
||||
import type { Package, Review, AirtablePost, Developer, Bottle } from '@tea/ui/types';
|
||||
import type { GUIPackage, Course, Category, AuthStatus } from '../types';
|
||||
import type { GUIPackage, Course, Category, AuthStatus, DeviceAuth } from '../types';
|
||||
|
||||
import * as mock from './mock';
|
||||
import { PackageStates } from '../types';
|
||||
import { getSession } from '$libs/stores/auth';
|
||||
import type { Session } from '$libs/stores/auth';
|
||||
import { getInstalledPackages } from '$libs/teaDir';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { installPackageCommand } from '$libs/cli';
|
||||
|
||||
export const apiBaseUrl = 'https://api.tea.xyz/v1';
|
||||
// export const apiBaseUrl = 'http://localhost:3000/v1';
|
||||
|
||||
async function getHeaders(path: string, session: Session) {
|
||||
const unixMs = new Date().getTime();
|
||||
const unixHexSecs = Math.round(unixMs / 1000).toString(16); // hex
|
||||
const deviceId = session.device_id?.split('-')[0];
|
||||
const preHash = [unixHexSecs, session.key, deviceId, path].join('');
|
||||
|
||||
const Authorization = bcrypt.hashSync(preHash, 10);
|
||||
|
||||
return {
|
||||
Authorization,
|
||||
['tea-ts']: unixMs.toString(),
|
||||
['tea-uid']: session.user?.developer_id,
|
||||
['tea-gui_id']: session.device_id
|
||||
};
|
||||
}
|
||||
|
||||
async function get<T>(path: string, query?: { [key: string]: string }) {
|
||||
const [session, client] = await Promise.all([getSession(), getClient()]);
|
||||
|
||||
const uri = join(apiBaseUrl, path);
|
||||
|
||||
const headers =
|
||||
session?.device_id && session?.user
|
||||
? await getHeaders(`GET/${path}`, session)
|
||||
: { Authorization: 'public ' };
|
||||
|
||||
const { data } = await client.get<T>(uri.toString(), {
|
||||
headers,
|
||||
query: query || {}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
const join = function (...paths: string[]) {
|
||||
return paths
|
||||
.map(function (path) {
|
||||
if (path[0] === '/') {
|
||||
path = path.slice(1);
|
||||
}
|
||||
if (path[path.length - 1] === '/') {
|
||||
path = path.slice(0, path.length - 1);
|
||||
}
|
||||
return path;
|
||||
})
|
||||
.join('/');
|
||||
};
|
||||
import { get as apiGet } from '$libs/v1Client';
|
||||
|
||||
export async function getPackages(): Promise<GUIPackage[]> {
|
||||
const [packages, installedPackages] = await Promise.all([
|
||||
get<Package[]>('packages'),
|
||||
apiGet<Package[]>('packages'),
|
||||
getInstalledPackages()
|
||||
]);
|
||||
|
||||
|
@ -98,7 +47,7 @@ export async function getFeaturedPackages(): Promise<Package[]> {
|
|||
|
||||
export async function getPackageReviews(full_name: string): Promise<Review[]> {
|
||||
console.log(`getting reviews for ${full_name}`);
|
||||
const reviews: Review[] = await get<Review[]>(
|
||||
const reviews: Review[] = await apiGet<Review[]>(
|
||||
`packages/${full_name.replaceAll('/', ':')}/reviews`
|
||||
);
|
||||
|
||||
|
@ -113,40 +62,8 @@ export async function installPackage(full_name: string) {
|
|||
}
|
||||
}
|
||||
|
||||
async function installPackageCommand(full_name: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const teaInstallCommand = new Command('tea-install', [`+${full_name}`, 'true']);
|
||||
teaInstallCommand.on('error', reject);
|
||||
|
||||
const handleLineOutput = async (line: string | { code: number }) => {
|
||||
const c = await child;
|
||||
if (
|
||||
(typeof line === 'string' && line.includes('installed:')) ||
|
||||
(typeof line !== 'string' && line?.code === 0)
|
||||
) {
|
||||
c.kill();
|
||||
resolve(c.pid);
|
||||
} else if (typeof line !== 'string' && line?.code === 1) {
|
||||
reject();
|
||||
}
|
||||
};
|
||||
|
||||
teaInstallCommand.stdout.on('data', handleLineOutput);
|
||||
teaInstallCommand.stderr.on('data', handleLineOutput);
|
||||
teaInstallCommand.on('close', (line: string) => {
|
||||
console.log('command closed!');
|
||||
handleLineOutput(line || '');
|
||||
});
|
||||
teaInstallCommand.on('error', (line: string) => {
|
||||
console.log('command error!', line);
|
||||
handleLineOutput(line || '');
|
||||
});
|
||||
const child = teaInstallCommand.spawn();
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFeaturedCourses(): Promise<Course[]> {
|
||||
const posts = await get<AirtablePost[]>('posts', { tag: 'featured_course' });
|
||||
const posts = await apiGet<AirtablePost[]>('posts', { tag: 'featured_course' });
|
||||
return posts.map((post) => {
|
||||
return {
|
||||
title: post.title,
|
||||
|
@ -164,36 +81,30 @@ export async function getTopPackages(): Promise<GUIPackage[]> {
|
|||
|
||||
export async function getAllPosts(tag?: string): Promise<AirtablePost[]> {
|
||||
// add filter here someday: tag = news | course
|
||||
const posts = await get<AirtablePost[]>('posts', tag ? { tag } : {});
|
||||
const posts = await apiGet<AirtablePost[]>('posts', tag ? { tag } : {});
|
||||
return posts;
|
||||
}
|
||||
|
||||
export async function getCategorizedPackages(): Promise<Category[]> {
|
||||
const categories = await get<Category[]>('/packages/categorized');
|
||||
const categories = await apiGet<Category[]>('/packages/categorized');
|
||||
return categories;
|
||||
}
|
||||
|
||||
type DeviceAuth = {
|
||||
status: AuthStatus;
|
||||
user: Developer;
|
||||
key: string;
|
||||
};
|
||||
|
||||
export async function getDeviceAuth(deviceId: string): Promise<DeviceAuth> {
|
||||
const data = await get<DeviceAuth>(`/auth/device/${deviceId}`);
|
||||
const data = await apiGet<DeviceAuth>(`/auth/device/${deviceId}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getPackageBottles(packageName: string): Promise<Bottle[]> {
|
||||
console.log('getting bottles for ', packageName);
|
||||
const client = await getClient();
|
||||
const uri = join('https://app.tea.xyz/api/bottles/', packageName);
|
||||
const uri = urlJoin('https://app.tea.xyz/api/bottles/', packageName);
|
||||
const { data } = await client.get<Bottle[]>(uri.toString());
|
||||
console.log('got bottles', data);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function registerDevice(): Promise<string> {
|
||||
const { deviceId } = await get<{ deviceId: string }>('/auth/registerDevice');
|
||||
const { deviceId } = await apiGet<{ deviceId: string }>('/auth/registerDevice');
|
||||
return deviceId;
|
||||
}
|
||||
|
|
33
modules/gui/src/libs/cli.ts
Normal file
33
modules/gui/src/libs/cli.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { Command } from '@tauri-apps/api/shell';
|
||||
|
||||
export async function installPackageCommand(full_name: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const teaInstallCommand = new Command('tea-install', [`+${full_name}`, 'true']);
|
||||
teaInstallCommand.on('error', reject);
|
||||
|
||||
const handleLineOutput = async (line: string | { code: number }) => {
|
||||
const c = await child;
|
||||
if (
|
||||
(typeof line === 'string' && line.includes('installed:')) ||
|
||||
(typeof line !== 'string' && line?.code === 0)
|
||||
) {
|
||||
c.kill();
|
||||
resolve(c.pid);
|
||||
} else if (typeof line !== 'string' && line?.code === 1) {
|
||||
reject();
|
||||
}
|
||||
};
|
||||
|
||||
teaInstallCommand.stdout.on('data', handleLineOutput);
|
||||
teaInstallCommand.stderr.on('data', handleLineOutput);
|
||||
teaInstallCommand.on('close', (line: string) => {
|
||||
console.log('command closed!');
|
||||
handleLineOutput(line || '');
|
||||
});
|
||||
teaInstallCommand.on('error', (line: string) => {
|
||||
console.log('command error!', line);
|
||||
handleLineOutput(line || '');
|
||||
});
|
||||
const child = teaInstallCommand.spawn();
|
||||
});
|
||||
}
|
|
@ -8,8 +8,6 @@ import { getPackages, getFeaturedPackages, getPackageReviews, getAllPosts } from
|
|||
import initAuthStore from './stores/auth';
|
||||
import initNavStore from './stores/nav';
|
||||
|
||||
export const backLink = writable<string>('/');
|
||||
|
||||
export const featuredPackages = writable<Package[]>([]);
|
||||
|
||||
function initPackagesStore() {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// please use the package @tea/ui/src/types.ts
|
||||
// things that go there are shared types/shapes like ie: Package
|
||||
|
||||
import type { Package } from '@tea/ui/types';
|
||||
import type { Package, Developer } from '@tea/ui/types';
|
||||
|
||||
export enum PackageStates {
|
||||
AVAILABLE,
|
||||
|
@ -36,3 +36,9 @@ export enum AuthStatus {
|
|||
SUCCESS = 'SUCCESS',
|
||||
FAILED = 'FAILED'
|
||||
}
|
||||
|
||||
export type DeviceAuth = {
|
||||
status: AuthStatus;
|
||||
user: Developer;
|
||||
key: string;
|
||||
};
|
||||
|
|
40
modules/gui/src/libs/v1Client.ts
Normal file
40
modules/gui/src/libs/v1Client.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { getClient } from '@tauri-apps/api/http';
|
||||
import type { Session } from '$libs/stores/auth';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { getSession } from '$libs/stores/auth';
|
||||
import urlJoin from 'url-join';
|
||||
|
||||
export const baseUrl = 'https://api.tea.xyz/v1';
|
||||
|
||||
export async function get<T>(path: string, query?: { [key: string]: string }) {
|
||||
const [session, client] = await Promise.all([getSession(), getClient()]);
|
||||
|
||||
const uri = urlJoin(baseUrl, path);
|
||||
|
||||
const headers =
|
||||
session?.device_id && session?.user
|
||||
? await getHeaders(`GET/${path}`, session)
|
||||
: { Authorization: 'public ' };
|
||||
|
||||
const { data } = await client.get<T>(uri.toString(), {
|
||||
headers,
|
||||
query: query || {}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
async function getHeaders(path: string, session: Session) {
|
||||
const unixMs = new Date().getTime();
|
||||
const unixHexSecs = Math.round(unixMs / 1000).toString(16); // hex
|
||||
const deviceId = session.device_id?.split('-')[0];
|
||||
const preHash = [unixHexSecs, session.key, deviceId, path].join('');
|
||||
|
||||
const Authorization = bcrypt.hashSync(preHash, 10);
|
||||
|
||||
return {
|
||||
Authorization,
|
||||
['tea-ts']: unixMs.toString(),
|
||||
['tea-uid']: session.user?.developer_id,
|
||||
['tea-gui_id']: session.device_id
|
||||
};
|
||||
}
|
|
@ -15,6 +15,7 @@ importers:
|
|||
'@tauri-apps/cli': ^1.2.2
|
||||
'@tea/ui': workspace:*
|
||||
'@types/bcryptjs': ^2.4.2
|
||||
'@types/url-join': ^4.0.1
|
||||
'@typescript-eslint/eslint-plugin': ^5.27.0
|
||||
'@typescript-eslint/parser': ^5.27.0
|
||||
autoprefixer: ^10.4.13
|
||||
|
@ -38,16 +39,19 @@ importers:
|
|||
tailwindcss: ^3.2.4
|
||||
tslib: ^2.3.1
|
||||
typescript: ^4.7.4
|
||||
url-join: ^5.0.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@tauri-apps/api': 1.2.0
|
||||
'@types/bcryptjs': 2.4.2
|
||||
'@types/url-join': 4.0.1
|
||||
bcryptjs: 2.4.3
|
||||
buffer: 6.0.3
|
||||
fuse.js: 6.6.2
|
||||
lodash: 4.17.21
|
||||
lorem-ipsum: 2.0.8
|
||||
svelte-watch-resize: 1.0.3
|
||||
url-join: 5.0.0
|
||||
devDependencies:
|
||||
'@playwright/test': 1.25.0
|
||||
'@sveltejs/adapter-auto': 1.0.0_@sveltejs+kit@1.0.1
|
||||
|
@ -3896,6 +3900,10 @@ packages:
|
|||
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
|
||||
dev: true
|
||||
|
||||
/@types/url-join/4.0.1:
|
||||
resolution: {integrity: sha512-wDXw9LEEUHyV+7UWy7U315nrJGJ7p1BzaCxDpEoLr789Dk1WDVMMlf3iBfbG2F8NdWnYyFbtTxUn2ZNbm1Q4LQ==}
|
||||
dev: false
|
||||
|
||||
/@types/webpack-env/1.18.0:
|
||||
resolution: {integrity: sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==}
|
||||
dev: true
|
||||
|
@ -10345,6 +10353,11 @@ packages:
|
|||
deprecated: Please see https://github.com/lydell/urix#deprecated
|
||||
dev: true
|
||||
|
||||
/url-join/5.0.0:
|
||||
resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dev: false
|
||||
|
||||
/use/3.1.1:
|
||||
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
Loading…
Reference in a new issue