test upload file to server
This commit is contained in:
parent
16e0e4f9d8
commit
aaa60216b2
43 changed files with 1814 additions and 285 deletions
|
|
@ -1,9 +1,9 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
"es2021": true,
|
es2021: true,
|
||||||
"node": true,
|
node: true,
|
||||||
"browser": false
|
browser: false
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
|
|
@ -11,23 +11,16 @@ module.exports = {
|
||||||
'plugin:react-hooks/recommended',
|
'plugin:react-hooks/recommended',
|
||||||
'prettier'
|
'prettier'
|
||||||
],
|
],
|
||||||
ignorePatterns: [
|
ignorePatterns: ['types/env.d.ts', 'node_modules/**', '**/dist/**', 'scr/components/ui/**'],
|
||||||
"types/env.d.ts",
|
|
||||||
"node_modules/**",
|
|
||||||
"**/dist/**",
|
|
||||||
],
|
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 12,
|
ecmaVersion: 12,
|
||||||
sourceType: 'module',
|
sourceType: 'module'
|
||||||
},
|
},
|
||||||
plugins: ['react-refresh', '@typescript-eslint', '@tanstack/eslint-plugin-query'],
|
plugins: ['react-refresh', '@typescript-eslint', '@tanstack/eslint-plugin-query'],
|
||||||
rules: {
|
rules: {
|
||||||
'react-refresh/only-export-components': [
|
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||||
'warn',
|
'@typescript-eslint/consistent-type-imports': 'error',
|
||||||
{ allowConstantExport: true },
|
'react-hooks/exhaustive-deps': 'off'
|
||||||
],
|
}
|
||||||
"@typescript-eslint/consistent-type-imports": "error",
|
|
||||||
"react-hooks/exhaustive-deps": "off",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,3 +49,7 @@ In this section, we will list all the environment variables. That app will use i
|
||||||
### TAOBIN_RECIPE_MANAGER_SERVER_URL
|
### TAOBIN_RECIPE_MANAGER_SERVER_URL
|
||||||
|
|
||||||
- The url of the server. Default: `http://localhost:8080`
|
- The url of the server. Default: `http://localhost:8080`
|
||||||
|
|
||||||
|
### TAOBIN_RECIPE_MANAGER_TUS_SERVER_URL
|
||||||
|
|
||||||
|
- The url of the tus server for upload/download files. Default: `http://localhost:8090/files`
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export default function (
|
||||||
picture: params.get('picture'),
|
picture: params.get('picture'),
|
||||||
permissions: params.get('permissions'),
|
permissions: params.get('permissions'),
|
||||||
access_token: params.get('access_token'),
|
access_token: params.get('access_token'),
|
||||||
max_age: params.get('max_age'),
|
max_age: params.get('access_token_max_age'),
|
||||||
refresh_token: params.get('refresh_token')
|
refresh_token: params.get('refresh_token')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { findCredentials, getPassword, setPassword } from '@postman/node-keytar'
|
import { findCredentials, getPassword, setPassword, deletePassword } from '@postman/node-keytar'
|
||||||
|
|
||||||
export function eventGetKeyChain(icpMain: Electron.IpcMain) {
|
export function eventGetKeyChain(icpMain: Electron.IpcMain) {
|
||||||
icpMain.on('get-keyChain', (event, serviceName, account) => {
|
icpMain.on('get-keyChain', (event, serviceName, account) => {
|
||||||
|
|
@ -7,12 +7,12 @@ export function eventGetKeyChain(icpMain: Electron.IpcMain) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
icpMain.on('set-keyChain', (_event, serviceName, account, password) => {
|
icpMain.on('set-keyChain', (_event, { serviceName, account, password }) => {
|
||||||
setPassword(serviceName, account, password)
|
setPassword(serviceName, account, password)
|
||||||
})
|
})
|
||||||
|
|
||||||
icpMain.on('delete-keyChain', (_event, serviceName, account) => {
|
icpMain.on('delete-keyChain', (_event, { serviceName, account }) => {
|
||||||
setPassword(serviceName, account, '')
|
deletePassword(serviceName, account)
|
||||||
})
|
})
|
||||||
|
|
||||||
icpMain.handle('keyChainSync', async (_event, serviceName) => {
|
icpMain.handle('keyChainSync', async (_event, serviceName) => {
|
||||||
|
|
|
||||||
8
client-electron/index.d.ts
vendored
Normal file
8
client-electron/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import 'react'
|
||||||
|
|
||||||
|
declare module 'react' {
|
||||||
|
// add webkitdirectory to input element React.InputHTMLAttributes<HTMLInputElement>
|
||||||
|
interface HTMLInputElement {
|
||||||
|
webkitdirectory?: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
1053
client-electron/package-lock.json
generated
1053
client-electron/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -17,7 +17,8 @@
|
||||||
"prebuild:electron": "rm -rf dist-renderer && rm -rf dist-electron && vite optimize -c vite.config.electron.ts -m production",
|
"prebuild:electron": "rm -rf dist-renderer && rm -rf dist-electron && vite optimize -c vite.config.electron.ts -m production",
|
||||||
"prebuild:web": "rm -rf dist-web && vite optimize -c vite.config.web.ts -m production",
|
"prebuild:web": "rm -rf dist-web && vite optimize -c vite.config.web.ts -m production",
|
||||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview:web": "vite preview -c vite.config.web.ts"
|
"preview:web": "vite preview -c vite.config.web.ts",
|
||||||
|
"postinstall": "electron-builder install-app-deps"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
|
|
@ -35,6 +36,14 @@
|
||||||
"@radix-ui/react-toast": "^1.1.5",
|
"@radix-ui/react-toast": "^1.1.5",
|
||||||
"@tanstack/react-query": "^5.17.19",
|
"@tanstack/react-query": "^5.17.19",
|
||||||
"@tanstack/react-table": "^8.11.7",
|
"@tanstack/react-table": "^8.11.7",
|
||||||
|
"@uppy/core": "^3.8.0",
|
||||||
|
"@uppy/dashboard": "^3.7.1",
|
||||||
|
"@uppy/drag-drop": "^3.0.3",
|
||||||
|
"@uppy/file-input": "^3.0.4",
|
||||||
|
"@uppy/golden-retriever": "^3.1.1",
|
||||||
|
"@uppy/progress-bar": "^3.0.4",
|
||||||
|
"@uppy/react": "^3.2.1",
|
||||||
|
"@uppy/tus": "^3.5.0",
|
||||||
"@yume-chan/adb": "^0.0.22",
|
"@yume-chan/adb": "^0.0.22",
|
||||||
"@yume-chan/adb-credential-web": "^0.0.22",
|
"@yume-chan/adb-credential-web": "^0.0.22",
|
||||||
"@yume-chan/adb-daemon-webusb": "^0.0.22",
|
"@yume-chan/adb-daemon-webusb": "^0.0.22",
|
||||||
|
|
@ -49,6 +58,7 @@
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-dropzone": "^14.2.3",
|
||||||
"react-hook-form": "^7.49.3",
|
"react-hook-form": "^7.49.3",
|
||||||
"react-router-dom": "^6.21.1",
|
"react-router-dom": "^6.21.1",
|
||||||
"sonner": "^1.3.1",
|
"sonner": "^1.3.1",
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,43 @@
|
||||||
import { createBrowserRouter, createHashRouter, RouterProvider, type RouteObject } from 'react-router-dom'
|
import { createBrowserRouter, createHashRouter, RouterProvider, type RouteObject } from 'react-router-dom'
|
||||||
import AuthCallBack from './AuthCallBack'
|
import AuthCallBack from './AuthCallBack'
|
||||||
import MainLayout from './layouts/MainLayout'
|
import MainLayout from './layouts/MainLayout'
|
||||||
import HomePage from './pages/Home'
|
import HomePage from './pages/home'
|
||||||
import LoginPage from './pages/Login'
|
import LoginPage from './pages/login'
|
||||||
import AndroidPage from './pages/Android'
|
import AndroidPage from './pages/android'
|
||||||
import RecipesPage from './pages/recipes/recipes'
|
import RecipesPage from './pages/recipes/recipes'
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
|
import UploadPage from './pages/upload'
|
||||||
|
import { type MenuList } from './components/sidebar'
|
||||||
|
import { DashboardIcon, FileTextIcon, RocketIcon, UploadIcon } from '@radix-ui/react-icons'
|
||||||
|
|
||||||
|
const sideBarMenuList: MenuList = [
|
||||||
|
{
|
||||||
|
title: 'Home',
|
||||||
|
icon: DashboardIcon,
|
||||||
|
link: '/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Recipes',
|
||||||
|
icon: FileTextIcon,
|
||||||
|
link: '/recipes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Android',
|
||||||
|
icon: RocketIcon,
|
||||||
|
link: '/android'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Upload',
|
||||||
|
icon: UploadIcon,
|
||||||
|
link: '/upload'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
function router() {
|
function router() {
|
||||||
const routes: RouteObject[] = [
|
const routes: RouteObject[] = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: <MainLayout />,
|
element: <MainLayout sidebarMenu={sideBarMenuList} />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
|
|
@ -24,6 +50,10 @@ function router() {
|
||||||
{
|
{
|
||||||
path: 'android',
|
path: 'android',
|
||||||
element: <AndroidPage />
|
element: <AndroidPage />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'upload',
|
||||||
|
element: <UploadPage />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,23 +5,27 @@ const AuthCallBack: React.FC = () => {
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
// emit message to main process
|
// emit message to main process
|
||||||
|
|
||||||
window.opener.postMessage(
|
if (params.get('kind') === 'electron') {
|
||||||
{
|
window.location.href = import.meta.env.TAOBIN_RECIPE_MANAGER_DEEPLINK_PROTOCOL + '/login' + window.location.search
|
||||||
payload: 'loginSuccess',
|
} else {
|
||||||
data: {
|
window.opener.postMessage(
|
||||||
id: params.get('id'),
|
{
|
||||||
name: params.get('name'),
|
payload: 'loginSuccess',
|
||||||
email: params.get('email'),
|
data: {
|
||||||
picture: params.get('picture'),
|
id: params.get('id'),
|
||||||
permissions: params.get('permissions')
|
name: params.get('name'),
|
||||||
}
|
email: params.get('email'),
|
||||||
},
|
picture: params.get('picture'),
|
||||||
'*'
|
permissions: params.get('permissions')
|
||||||
)
|
}
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
}
|
||||||
|
|
||||||
return <div>it just call back</div>
|
return <div>Login Success. You can close this window now.</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AuthCallBack
|
export default AuthCallBack
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,32 @@
|
||||||
import { DashboardIcon, FileTextIcon, RocketIcon } from '@radix-ui/react-icons'
|
import React from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
const Sidebar: React.FC = () => {
|
export type MenuList = {
|
||||||
|
title: string
|
||||||
|
icon: React.ComponentType<{ className?: string }>
|
||||||
|
link: string
|
||||||
|
}[]
|
||||||
|
|
||||||
|
interface SideBarProps {
|
||||||
|
menuList: MenuList
|
||||||
|
}
|
||||||
|
|
||||||
|
const Sidebar: React.FC<SideBarProps> = ({ menuList }) => {
|
||||||
return (
|
return (
|
||||||
<aside className="fixed top-0 left-0 z-40 w-64 pt-20 h-screen">
|
<aside className="fixed top-0 left-0 z-40 w-64 pt-20 h-screen">
|
||||||
<div className="h-full px-3 pb-4 overflow-y-auto bg-zinc-700">
|
<div className="h-full px-3 pb-4 overflow-y-auto bg-zinc-700">
|
||||||
<ul className="space-y-2 font-medium">
|
<ul className="space-y-2 font-medium">
|
||||||
<li>
|
{menuList.map((item, index) => (
|
||||||
<Link to="/" className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500">
|
<li key={index}>
|
||||||
<DashboardIcon className="inline-block w-6 h-6 mr-3" />
|
<Link
|
||||||
Dashboard
|
to={item.link}
|
||||||
</Link>
|
className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500"
|
||||||
</li>
|
>
|
||||||
<li>
|
{<item.icon className="inline-block w-6 h-6 mr-3" />}
|
||||||
<Link to="/recipes" className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500">
|
{item.title}
|
||||||
<FileTextIcon className="inline-block w-6 h-6 mr-3" />
|
</Link>
|
||||||
Recipes
|
</li>
|
||||||
</Link>
|
))}
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link to="/android" className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500">
|
|
||||||
<RocketIcon className="inline-block w-6 h-6 mr-3" />
|
|
||||||
Android
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
|
||||||
76
client-electron/src/components/ui/card.tsx
Normal file
76
client-electron/src/components/ui/card.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Card = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"rounded-xl border bg-card text-card-foreground shadow",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Card.displayName = "Card"
|
||||||
|
|
||||||
|
const CardHeader = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardHeader.displayName = "CardHeader"
|
||||||
|
|
||||||
|
const CardTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h3
|
||||||
|
ref={ref}
|
||||||
|
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
|
const CardDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
|
const CardContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||||
|
))
|
||||||
|
CardContent.displayName = "CardContent"
|
||||||
|
|
||||||
|
const CardFooter = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex items-center p-6 pt-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardFooter.displayName = "CardFooter"
|
||||||
|
|
||||||
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||||
71
client-electron/src/components/ui/drop-zone.tsx
Normal file
71
client-electron/src/components/ui/drop-zone.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { Card, CardContent } from '@/components/ui/card'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import React, { type ChangeEvent, useRef } from 'react'
|
||||||
|
|
||||||
|
interface DropzoneProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange'> {
|
||||||
|
classNameWrapper?: string
|
||||||
|
className?: string
|
||||||
|
dropMessage: string
|
||||||
|
handleOnDrop: (acceptedFiles: FileList | null) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dropzone = React.forwardRef<HTMLDivElement, DropzoneProps>(
|
||||||
|
({ className, classNameWrapper, dropMessage, handleOnDrop, ...props }, ref) => {
|
||||||
|
const inputRef = useRef<HTMLInputElement | null>(null)
|
||||||
|
// Function to handle drag over event
|
||||||
|
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
handleOnDrop(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to handle drop event
|
||||||
|
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
const { files } = e.dataTransfer
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.files = files
|
||||||
|
handleOnDrop(files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to simulate a click on the file input element
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
`border-2 border-dashed bg-muted hover:cursor-pointer hover:border-muted-foreground/50`,
|
||||||
|
classNameWrapper
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CardContent
|
||||||
|
className="flex flex-col items-center justify-center space-y-2 px-2 py-4 text-xs"
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center text-muted-foreground">
|
||||||
|
<span className="font-medium">{dropMessage}</span>
|
||||||
|
<Input
|
||||||
|
{...props}
|
||||||
|
value={undefined}
|
||||||
|
ref={inputRef}
|
||||||
|
type="file"
|
||||||
|
className={cn('hidden', className)}
|
||||||
|
onChange={(e: ChangeEvent<HTMLInputElement>) => handleOnDrop(e.target.files)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Dropzone
|
||||||
15
client-electron/src/constants/permissions.ts
Normal file
15
client-electron/src/constants/permissions.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
export enum PermissionEnum {
|
||||||
|
THAI_PERMISSIONS = 1,
|
||||||
|
MALAY_PERMISSIONS = 1 << 1,
|
||||||
|
AUS_PERMISSIONS = 1 << 2,
|
||||||
|
ALPHA3_PERMISSIONS = 1 << 3,
|
||||||
|
|
||||||
|
VIEWER_PERMISSIONS = 1 << 4,
|
||||||
|
EDITOR_PERMISSIONS = VIEWER_PERMISSIONS << 1,
|
||||||
|
|
||||||
|
SUPER_ADMIN = THAI_PERMISSIONS |
|
||||||
|
MALAY_PERMISSIONS |
|
||||||
|
AUS_PERMISSIONS |
|
||||||
|
ALPHA3_PERMISSIONS |
|
||||||
|
(VIEWER_PERMISSIONS | EDITOR_PERMISSIONS)
|
||||||
|
}
|
||||||
15
client-electron/src/constants/recipe.ts
Normal file
15
client-electron/src/constants/recipe.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { CheckCircledIcon, CrossCircledIcon, QuestionMarkCircledIcon } from '@radix-ui/react-icons'
|
||||||
|
|
||||||
|
export enum RecipeStatus {
|
||||||
|
DISABLE = 'DISABLE',
|
||||||
|
ENABLE = 'ENABLE'
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusIcons = {
|
||||||
|
[RecipeStatus.DISABLE]: CrossCircledIcon,
|
||||||
|
[RecipeStatus.ENABLE]: CheckCircledIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRecipeStatusIcon = (status: RecipeStatus) => {
|
||||||
|
return statusIcons[status] || QuestionMarkCircledIcon
|
||||||
|
}
|
||||||
16
client-electron/src/hooks/recipe/get-recipe-overview.ts
Normal file
16
client-electron/src/hooks/recipe/get-recipe-overview.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import customAxios from '@/lib/customAxios'
|
||||||
|
import { type RecipeOverview } from '@/models/recipe/schema'
|
||||||
|
|
||||||
|
interface GetRecipeOverviewFilterQuery {
|
||||||
|
countryID: string
|
||||||
|
filename: string
|
||||||
|
materialIDs: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRecipeOverview = (query?: GetRecipeOverviewFilterQuery): Promise<RecipeOverview[]> => {
|
||||||
|
return customAxios
|
||||||
|
.get<RecipeOverview[]>(import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL + '/recipe/overview', { params: query })
|
||||||
|
.then(res => {
|
||||||
|
return res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,11 @@
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import customAxios from '../lib/customAxios'
|
import customAxios from '../lib/customAxios'
|
||||||
|
import type { User } from '@/models/user/schema'
|
||||||
export interface UserInfo {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
email: string
|
|
||||||
picture: string
|
|
||||||
permissions: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UserAuth {
|
interface UserAuth {
|
||||||
userInfo: UserInfo | null
|
userInfo: User | null
|
||||||
setUserInfo: (userInfo: UserInfo | null) => void
|
setUserInfo: (userInfo: User | null) => void
|
||||||
getUserInfo: () => Promise<UserInfo | null>
|
getUserInfo: () => Promise<User | null>
|
||||||
logout: () => void
|
logout: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,7 +14,7 @@ const userAuthStore = create<UserAuth>(set => ({
|
||||||
setUserInfo: userInfo => set({ userInfo }),
|
setUserInfo: userInfo => set({ userInfo }),
|
||||||
getUserInfo: () => {
|
getUserInfo: () => {
|
||||||
return customAxios
|
return customAxios
|
||||||
.get<UserInfo>('/user/me')
|
.get<User>('/user/me')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
set({ userInfo: res.data })
|
set({ userInfo: res.data })
|
||||||
return res.data
|
return res.data
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
import { Outlet, useNavigate } from 'react-router-dom'
|
import { Outlet, useNavigate } from 'react-router-dom'
|
||||||
import Navbar from '../components/Header'
|
import Navbar from '../components/header'
|
||||||
import Sidebar from '../components/Sidebar'
|
import Sidebar, { type MenuList } from '../components/sidebar'
|
||||||
import userAuthStore from '../hooks/userAuth'
|
import userAuthStore from '../hooks/userAuth'
|
||||||
import { useShallow } from 'zustand/react/shallow'
|
import { useShallow } from 'zustand/react/shallow'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { Toaster } from '@/components/ui/toaster'
|
import { Toaster } from '@/components/ui/toaster'
|
||||||
|
|
||||||
const MainLayout = () => {
|
interface MainLayoutProps {
|
||||||
|
sidebarMenu: MenuList
|
||||||
|
}
|
||||||
|
|
||||||
|
const MainLayout: React.FC<MainLayoutProps> = ({ sidebarMenu }) => {
|
||||||
const { userInfo, getUserInfo } = userAuthStore(
|
const { userInfo, getUserInfo } = userAuthStore(
|
||||||
useShallow(state => ({
|
useShallow(state => ({
|
||||||
userInfo: state.userInfo,
|
userInfo: state.userInfo,
|
||||||
|
|
@ -33,7 +37,7 @@ const MainLayout = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<Sidebar />
|
<Sidebar menuList={sidebarMenu} />
|
||||||
<main className="p-8 sm:ml-64 mt-20">
|
<main className="p-8 sm:ml-64 mt-20">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
const customAxios = axios.create({
|
const customAxios = axios.create({
|
||||||
baseURL: import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL,
|
baseURL: import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL ?? 'http://localhost:8080',
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
32
client-electron/src/lib/permissions.ts
Normal file
32
client-electron/src/lib/permissions.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { PermissionEnum } from '@/constants/permissions'
|
||||||
|
|
||||||
|
export function getPermissions(permissions: number): PermissionEnum[] {
|
||||||
|
const permissionsArray: PermissionEnum[] = []
|
||||||
|
if (permissions & PermissionEnum.THAI_PERMISSIONS) {
|
||||||
|
permissionsArray.push(PermissionEnum.THAI_PERMISSIONS)
|
||||||
|
}
|
||||||
|
if (permissions & PermissionEnum.MALAY_PERMISSIONS) {
|
||||||
|
permissionsArray.push(PermissionEnum.MALAY_PERMISSIONS)
|
||||||
|
}
|
||||||
|
if (permissions & PermissionEnum.AUS_PERMISSIONS) {
|
||||||
|
permissionsArray.push(PermissionEnum.AUS_PERMISSIONS)
|
||||||
|
}
|
||||||
|
if (permissions & PermissionEnum.ALPHA3_PERMISSIONS) {
|
||||||
|
permissionsArray.push(PermissionEnum.ALPHA3_PERMISSIONS)
|
||||||
|
}
|
||||||
|
if (permissions & PermissionEnum.VIEWER_PERMISSIONS) {
|
||||||
|
permissionsArray.push(PermissionEnum.VIEWER_PERMISSIONS)
|
||||||
|
}
|
||||||
|
if (permissions & PermissionEnum.EDITOR_PERMISSIONS) {
|
||||||
|
permissionsArray.push(PermissionEnum.EDITOR_PERMISSIONS)
|
||||||
|
}
|
||||||
|
return permissionsArray
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasPermission(permissions: number, permission: PermissionEnum): boolean {
|
||||||
|
return Boolean(permissions & permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function permissionsToNumber(permissions: PermissionEnum[]): number {
|
||||||
|
return permissions.reduce((acc, permission) => acc | permission, 0)
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { type ClassValue, clsx } from "clsx"
|
import { type ClassValue, clsx } from 'clsx'
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,7 @@ if (window.electronRuntime) {
|
||||||
})
|
})
|
||||||
|
|
||||||
window.ipcRenderer
|
window.ipcRenderer
|
||||||
.invoke(
|
.invoke('keyChainSync', import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME)
|
||||||
'keyChainSync',
|
|
||||||
import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME
|
|
||||||
)
|
|
||||||
.then(result => {
|
.then(result => {
|
||||||
console.log(result)
|
console.log(result)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
20
client-electron/src/models/recipe/schema.ts
Normal file
20
client-electron/src/models/recipe/schema.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { RecipeStatus } from '@/constants/recipe'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const taskSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
status: z.string(),
|
||||||
|
label: z.string(),
|
||||||
|
priority: z.string()
|
||||||
|
})
|
||||||
|
export type Task = z.infer<typeof taskSchema>
|
||||||
|
|
||||||
|
export const recipeOverviewSchema = z.object({
|
||||||
|
productCode: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
otherName: z.string(),
|
||||||
|
status: z.nativeEnum(RecipeStatus),
|
||||||
|
lastUpdated: z.date()
|
||||||
|
})
|
||||||
|
export type RecipeOverview = z.infer<typeof recipeOverviewSchema>
|
||||||
10
client-electron/src/models/user/schema.ts
Normal file
10
client-electron/src/models/user/schema.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const userSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
email: z.string().email(),
|
||||||
|
picture: z.string().url(),
|
||||||
|
permissions: z.number()
|
||||||
|
})
|
||||||
|
|
||||||
|
export type User = z.infer<typeof userSchema>
|
||||||
|
|
@ -82,7 +82,9 @@ const AndroidPage: React.FC = () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
async function scrcpyConnect() {
|
async function scrcpyConnect() {
|
||||||
const server: ArrayBuffer = await fetch(new URL('../scrcpy/scrcpy_server_v1.25', import.meta.url)).then(res => res.arrayBuffer())
|
const server: ArrayBuffer = await fetch(new URL('../scrcpy/scrcpy_server_v1.25', import.meta.url)).then(res =>
|
||||||
|
res.arrayBuffer()
|
||||||
|
)
|
||||||
|
|
||||||
await AdbScrcpyClient.pushServer(
|
await AdbScrcpyClient.pushServer(
|
||||||
adb!,
|
adb!,
|
||||||
|
|
@ -94,7 +96,9 @@ const AndroidPage: React.FC = () => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const res = await adb!.subprocess.spawn('CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 1.25')
|
const res = await adb!.subprocess.spawn(
|
||||||
|
'CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 1.25'
|
||||||
|
)
|
||||||
|
|
||||||
res.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(
|
res.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(
|
||||||
new WritableStream({
|
new WritableStream({
|
||||||
|
|
@ -111,7 +115,12 @@ const AndroidPage: React.FC = () => {
|
||||||
control: true,
|
control: true,
|
||||||
logLevel: ScrcpyLogLevel1_18.Debug
|
logLevel: ScrcpyLogLevel1_18.Debug
|
||||||
})
|
})
|
||||||
const _client = await AdbScrcpyClient.start(adb!, '/data/local/tmp/scrcpy-server.jar', '1.25', new AdbScrcpyOptions1_22(scrcpyOption))
|
const _client = await AdbScrcpyClient.start(
|
||||||
|
adb!,
|
||||||
|
'/data/local/tmp/scrcpy-server.jar',
|
||||||
|
'1.25',
|
||||||
|
new AdbScrcpyOptions1_22(scrcpyOption)
|
||||||
|
)
|
||||||
|
|
||||||
const videoStream: AdbScrcpyVideoStream | undefined = await _client?.videoStream
|
const videoStream: AdbScrcpyVideoStream | undefined = await _client?.videoStream
|
||||||
|
|
||||||
|
|
@ -241,7 +250,7 @@ const AndroidPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center mx-auto">
|
<div className="flex flex-col justify-center items-center mx-auto">
|
||||||
<div className="flex justify-around items-start mx-auto w-full h-full">
|
<div className="flex justify-around items-start mx-auto w-full h-full">
|
||||||
<div>
|
<div>
|
||||||
<div ref={screenRef} className="min-h-[700px] min-w-[400px] max-h-[700px] max-w-[400px] bg-gray-700"></div>
|
<div ref={screenRef} className="min-h-[700px] min-w-[400px] max-h-[700px] max-w-[400px] bg-gray-700"></div>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import googleLogo from '../assets/google-color.svg'
|
import googleLogo from '@/assets/google-color.svg'
|
||||||
import userAuthStore, { type UserInfo } from '../hooks/userAuth'
|
import userAuthStore from '@/hooks/userAuth'
|
||||||
|
import { userSchema } from '@/models/user/schema'
|
||||||
|
|
||||||
const LoginPage: React.FC = () => {
|
const LoginPage: React.FC = () => {
|
||||||
const setUserInfo = userAuthStore(state => state.setUserInfo)
|
const setUserInfo = userAuthStore(state => state.setUserInfo)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const redirectUrl =
|
const redirectUrl = new URLSearchParams(window.location.search).get('redirect_to') ?? '/'
|
||||||
new URLSearchParams(window.location.search).get('redirect_to') ?? '/'
|
|
||||||
|
|
||||||
const loginWithGoogle = () => {
|
const loginWithGoogle = () => {
|
||||||
// if is web mode then use window.open
|
// if is web mode then use window.open
|
||||||
|
|
@ -15,24 +15,35 @@ const LoginPage: React.FC = () => {
|
||||||
if (window.electronRuntime) {
|
if (window.electronRuntime) {
|
||||||
window.ipcRenderer.send(
|
window.ipcRenderer.send(
|
||||||
'deeplink',
|
'deeplink',
|
||||||
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL +
|
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL + '/auth/google?redirect_to=' + redirectUrl + '&kind=electron'
|
||||||
'/auth/google?redirect_to=' +
|
|
||||||
redirectUrl +
|
|
||||||
'&kind=electron'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
window.ipcRenderer.on('loginSuccess', (_event, data) => {
|
window.ipcRenderer.on('loginSuccess', (_event, data) => {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
|
|
||||||
setUserInfo(data satisfies UserInfo)
|
const { access_token, max_age, refresh_token } = data
|
||||||
|
|
||||||
|
window.ipcRenderer.send('set-keyChain', {
|
||||||
|
serviceName: import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME,
|
||||||
|
account: import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_ACCOUNT_ACCESS_TOKEN,
|
||||||
|
password: access_token + ';' + max_age
|
||||||
|
})
|
||||||
|
|
||||||
|
window.ipcRenderer.send('set-keyChain', {
|
||||||
|
serviceName: import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME,
|
||||||
|
account: import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_ACCOUNT_REFRESH_TOKEN,
|
||||||
|
password: refresh_token
|
||||||
|
})
|
||||||
|
|
||||||
|
data.permissions = Number(data.permissions)
|
||||||
|
const user = userSchema.parse(data)
|
||||||
|
setUserInfo(user)
|
||||||
navigate(redirectUrl)
|
navigate(redirectUrl)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// open new window and listen to message from window.opener
|
// open new window and listen to message from window.opener
|
||||||
window.open(
|
window.open(
|
||||||
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL +
|
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL + '/auth/google?redirect_to=' + redirectUrl,
|
||||||
'/auth/google?redirect_to=' +
|
|
||||||
redirectUrl,
|
|
||||||
'_blank',
|
'_blank',
|
||||||
'width=500,height=600'
|
'width=500,height=600'
|
||||||
)
|
)
|
||||||
|
|
@ -40,22 +51,8 @@ const LoginPage: React.FC = () => {
|
||||||
// listen to message from new window
|
// listen to message from new window
|
||||||
window.addEventListener('message', event => {
|
window.addEventListener('message', event => {
|
||||||
if (event.data.payload === 'loginSuccess') {
|
if (event.data.payload === 'loginSuccess') {
|
||||||
// const { access_token, max_age, refresh_token } = event.data.data
|
const user = userSchema.parse(event.data.data)
|
||||||
// setPassword(
|
setUserInfo(user)
|
||||||
// import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME,
|
|
||||||
// import.meta.env
|
|
||||||
// .TAOBIN_RECIPE_MANAGER_KEY_CHAIN_ACCOUNT_ACCESS_TOKEN,
|
|
||||||
// access_token + ';' + max_age
|
|
||||||
// )
|
|
||||||
|
|
||||||
// setPassword(
|
|
||||||
// import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME,
|
|
||||||
// import.meta.env
|
|
||||||
// .TAOBIN_RECIPE_MANAGER_KEY_CHAIN_ACCOUNT_REFRESH_TOKEN,
|
|
||||||
// refresh_token
|
|
||||||
// )
|
|
||||||
|
|
||||||
setUserInfo(event.data.data satisfies UserInfo)
|
|
||||||
|
|
||||||
navigate(redirectUrl)
|
navigate(redirectUrl)
|
||||||
}
|
}
|
||||||
|
|
@ -64,17 +61,12 @@ const LoginPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-screen">
|
<div className="flex h-screen flex-col items-center justify-center">
|
||||||
<button
|
<button
|
||||||
onClick={loginWithGoogle}
|
onClick={loginWithGoogle}
|
||||||
className="bg-white px-4 py-2 border flex gap-2 border-slate-200 rounded-lg text-slate-700 hover:border-slate-400 hover:text-slate-900 hover:shadow transition duration-150"
|
className="flex gap-2 rounded-lg border border-slate-200 bg-white px-4 py-2 text-slate-700 transition duration-150 hover:border-slate-400 hover:text-slate-900 hover:shadow"
|
||||||
>
|
>
|
||||||
<img
|
<img className="h-6 w-6" src={googleLogo} alt="google logo" loading="eager" />
|
||||||
className="w-6 h-6"
|
|
||||||
src={googleLogo}
|
|
||||||
alt="google logo"
|
|
||||||
loading="eager"
|
|
||||||
/>
|
|
||||||
<span>Login with @forth.co.th Google account</span>
|
<span>Login with @forth.co.th Google account</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { type ColumnDef } from '@tanstack/react-table'
|
import { type ColumnDef } from '@tanstack/react-table'
|
||||||
import { type Task } from '../models/schema'
|
import { type RecipeOverview } from '@/models/recipe/schema'
|
||||||
import { Checkbox } from '@/components/ui/checkbox'
|
import { Checkbox } from '@/components/ui/checkbox'
|
||||||
import DataTableColumnHeader from './data-table-column-header'
|
import DataTableColumnHeader from './data-table-column-header'
|
||||||
import { labels, priorities, statuses } from '../models/data'
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import DataTableRowActions from './data-table-row-actions'
|
import DataTableRowActions from './data-table-row-actions'
|
||||||
|
import { type RecipeStatus, getRecipeStatusIcon } from '@/constants/recipe'
|
||||||
|
|
||||||
export const columns: ColumnDef<Task>[] = [
|
export const columns: ColumnDef<RecipeOverview>[] = [
|
||||||
{
|
{
|
||||||
id: 'select',
|
id: 'select',
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
|
|
@ -29,22 +29,22 @@ export const columns: ColumnDef<Task>[] = [
|
||||||
enableHiding: false
|
enableHiding: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'id',
|
accessorKey: 'productCode',
|
||||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Task" />,
|
header: ({ column }) => <DataTableColumnHeader column={column} title="ProductCode" />,
|
||||||
cell: ({ row }) => <div className="w-[80px]">{row.getValue('id')}</div>,
|
cell: ({ row }) => <div className="w-[80px]">{row.getValue('productCode')}</div>,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableHiding: false
|
enableHiding: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'title',
|
accessorKey: 'name',
|
||||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Title" />,
|
header: ({ column }) => <DataTableColumnHeader column={column} title="Name" />,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const label = labels.find(label => label.value === row.original.label)
|
const label = { label: 'Test Label' } // labels.find(label => label.value === row.getValue('label'))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
{label && <Badge variant="outline">{label.label}</Badge>}
|
{label && <Badge variant="outline">{label.label}</Badge>}
|
||||||
<span className="max-w-[500px] truncate font-medium">{row.getValue('title')}</span>
|
<span className="max-w-[500px] truncate font-medium">{row.getValue('name')}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -53,16 +53,16 @@ export const columns: ColumnDef<Task>[] = [
|
||||||
accessorKey: 'status',
|
accessorKey: 'status',
|
||||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Status" />,
|
header: ({ column }) => <DataTableColumnHeader column={column} title="Status" />,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const status = statuses.find(status => status.value === row.getValue('status'))
|
const status: RecipeStatus = row.getValue('status')
|
||||||
|
const StatusIcon = getRecipeStatusIcon(status)
|
||||||
if (!status) {
|
if (!status) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-[100px] items-center">
|
<div className="flex w-[100px] items-center">
|
||||||
{status.icon && <status.icon className="mr-2 h-4 w-4 text-muted-foreground" />}
|
{<StatusIcon className="text-muted-foreground mr-2 h-4 w-4" />}
|
||||||
<span>{status.label}</span>
|
<span>{}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -71,19 +71,12 @@ export const columns: ColumnDef<Task>[] = [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'priority',
|
accessorKey: 'lastUpdated',
|
||||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Priority" />,
|
header: ({ column }) => <DataTableColumnHeader column={column} title="Last Updated" />,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const priority = priorities.find(priority => priority.value === row.getValue('priority'))
|
|
||||||
|
|
||||||
if (!priority) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{priority.icon && <priority.icon className="mr-2 h-4 w-4 text-muted-foreground" />}
|
<span>{row.getValue('lastUpdated')}</span>
|
||||||
<span>{priority.label}</span>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { type Row } from '@tanstack/react-table'
|
import { type Row } from '@tanstack/react-table'
|
||||||
import { taskSchema } from '../models/schema'
|
import { taskSchema } from '@/models/recipe/schema'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { DotsHorizontalIcon } from '@radix-ui/react-icons'
|
import { DotsHorizontalIcon } from '@radix-ui/react-icons'
|
||||||
import { labels } from '../models/data'
|
import { labels } from '@/models/data'
|
||||||
|
|
||||||
interface DataTableRowActionsProps<TData> {
|
interface DataTableRowActionsProps<TData> {
|
||||||
row: Row<TData>
|
row: Row<TData>
|
||||||
|
|
@ -27,7 +27,7 @@ const DataTableRowActions = <TData,>({ row }: DataTableRowActionsProps<TData>) =
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" className="flex h-8 w-8 p-0 data-[state=open]:bg-muted">
|
<Button variant="ghost" className="data-[state=open]:bg-muted flex h-8 w-8 p-0">
|
||||||
<DotsHorizontalIcon className="h-4 w-4" />
|
<DotsHorizontalIcon className="h-4 w-4" />
|
||||||
<span className="sr-only">Open menu</span>
|
<span className="sr-only">Open menu</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { type Table } from '@tanstack/react-table'
|
import { type Table } from '@tanstack/react-table'
|
||||||
import { statuses, priorities } from '../models/data'
|
import { statuses, priorities } from '@/models/data'
|
||||||
import DataTableFacetedFilter from './data-table-faceted-filter'
|
import DataTableFacetedFilter from './data-table-faceted-filter'
|
||||||
import DataTableViewOptions from './data-table-view-options'
|
import DataTableViewOptions from './data-table-view-options'
|
||||||
import { Cross2Icon } from '@radix-ui/react-icons'
|
import { Cross2Icon } from '@radix-ui/react-icons'
|
||||||
|
|
@ -15,7 +15,7 @@ const DataTableToolbar = <TData,>({ table }: DataTableToolbarProps<TData>) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex flex-col w-full rounded-lg bg-white shadow-md p-3 space-y-4">
|
<div className="flex w-full flex-col space-y-4 rounded-lg bg-white p-3 shadow-md">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex flex-1 items-center space-x-2">
|
<div className="flex flex-1 items-center space-x-2">
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import { z } from 'zod'
|
|
||||||
|
|
||||||
export const taskSchema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
title: z.string(),
|
|
||||||
status: z.string(),
|
|
||||||
label: z.string(),
|
|
||||||
priority: z.string()
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Task = z.infer<typeof taskSchema>
|
|
||||||
|
|
@ -1,30 +1,21 @@
|
||||||
import { type Task, taskSchema } from './models/schema'
|
|
||||||
import { z } from 'zod'
|
|
||||||
import DataTable from './components/data-table'
|
|
||||||
import { columns } from './components/columns'
|
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { columns } from './components/columns'
|
||||||
// Simulate a database read for tasks.
|
import DataTable from './components/data-table'
|
||||||
async function getTasks() {
|
import { getRecipeOverview } from '@/hooks/recipe/get-recipe-overview'
|
||||||
const data = await fetch(new URL('./models/tasks.json', import.meta.url)).then(res => res.text())
|
|
||||||
|
|
||||||
const tasks = JSON.parse(data.toString())
|
|
||||||
|
|
||||||
return z.array(taskSchema).parse(tasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
const RecipesPage = () => {
|
const RecipesPage = () => {
|
||||||
const { data: tasks, isLoading } = useQuery({ queryKey: ['tasks'], queryFn: getTasks })
|
const { data: recipeOverviewList, isLoading } = useQuery({
|
||||||
|
queryKey: ['recipe-overview'],
|
||||||
console.log('here')
|
queryFn: () => getRecipeOverview()
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full gap-3">
|
<div className="flex w-full flex-col gap-3">
|
||||||
<section>
|
<section>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Recipes</h1>
|
<h1 className="text-3xl font-bold text-gray-900">Recipes</h1>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<DataTable data={tasks} columns={columns} isLoading={isLoading} />
|
<DataTable data={recipeOverviewList ?? []} columns={columns} isLoading={isLoading} />
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
46
client-electron/src/pages/upload.tsx
Normal file
46
client-electron/src/pages/upload.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import Uppy from '@uppy/core'
|
||||||
|
import { Dashboard } from '@uppy/react'
|
||||||
|
import GoldenRetriever from '@uppy/golden-retriever'
|
||||||
|
import Tus from '@uppy/tus'
|
||||||
|
|
||||||
|
import '@uppy/core/dist/style.min.css'
|
||||||
|
import '@uppy/dashboard/dist/style.min.css'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const UploadPage: React.FC = () => {
|
||||||
|
const [uppy] = useState(() =>
|
||||||
|
new Uppy().use(GoldenRetriever, { serviceWorker: true }).use(Tus, {
|
||||||
|
endpoint: import.meta.env.TAOBIN_RECIPE_MANAGER_TUS_SERVER_URL ?? 'http://localhost:8090/files/',
|
||||||
|
withCredentials: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const [files, setFiles] = useState([])
|
||||||
|
|
||||||
|
async function getFiles() {
|
||||||
|
const files = await axios.get('http://localhost:8090/list').then(res => res.data)
|
||||||
|
console.log(files)
|
||||||
|
setFiles(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getFiles()
|
||||||
|
}, [])
|
||||||
|
uppy.on('upload-success', () => {
|
||||||
|
getFiles()
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Dashboard uppy={uppy} proudlyDisplayPoweredByUppy={false} showProgressDetails={true} />
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{files.map((file, index) => (
|
||||||
|
<div key={index}>{JSON.stringify(file)}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UploadPage
|
||||||
1
client-electron/src/vite-env.d.ts
vendored
1
client-electron/src/vite-env.d.ts
vendored
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
TAOBIN_RECIPE_MANAGER_SERVER_URL: string
|
TAOBIN_RECIPE_MANAGER_SERVER_URL: string
|
||||||
|
TAOBIN_RECIPE_MANAGER_TUS_SERVER_URL: string
|
||||||
TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME: string
|
TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME: string
|
||||||
TAOBIN_RECIPE_MANAGER_KEY_CHAIN_ACCOUNT_ACCESS_TOKEN: string
|
TAOBIN_RECIPE_MANAGER_KEY_CHAIN_ACCOUNT_ACCESS_TOKEN: string
|
||||||
TAOBIN_RECIPE_MANAGER_KEY_CHAIN_ACCOUNT_REFRESH_TOKEN: string
|
TAOBIN_RECIPE_MANAGER_KEY_CHAIN_ACCOUNT_REFRESH_TOKEN: string
|
||||||
|
|
|
||||||
3
server/.gitignore
vendored
3
server/.gitignore
vendored
|
|
@ -4,4 +4,5 @@
|
||||||
token.json
|
token.json
|
||||||
client_secret.json
|
client_secret.json
|
||||||
app.env
|
app.env
|
||||||
cofffeemachineConfig
|
cofffeemachineConfig
|
||||||
|
/uploads/*
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 85d4ada5c95a77803281ef76b1421807d9203353
|
Subproject commit 6e0d512afc4667a2204ff773ad8a3851120cdadd
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
ServerPort uint `mapstructure:"SERVER_PORT"`
|
ServerPort int `mapstructure:"SERVER_PORT"`
|
||||||
|
TusServerPort int `mapstructure:"TUS_SERVER_PORT"`
|
||||||
AllowedOrigins string `mapstructure:"ALLOWED_ORIGINS"`
|
AllowedOrigins string `mapstructure:"ALLOWED_ORIGINS"`
|
||||||
ClientRedirectURL string `mapstructure:"CLIENT_REDIRECT_URL"`
|
ClientRedirectURL string `mapstructure:"CLIENT_REDIRECT_URL"`
|
||||||
ClientElectronRedirectURL string `mapstructure:"CLIENT_ELECTRON_REDIRECT_URL"`
|
ClientElectronRedirectURL string `mapstructure:"CLIENT_ELECTRON_REDIRECT_URL"`
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -5,26 +5,31 @@ go 1.21.1
|
||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.0.10
|
github.com/go-chi/chi/v5 v5.0.10
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/gorilla/websocket v1.5.0
|
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
github.com/jmoiron/sqlx v1.3.5
|
||||||
github.com/mattn/go-sqlite3 v1.14.18
|
github.com/mattn/go-sqlite3 v1.14.18
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
golang.org/x/oauth2 v0.12.0
|
github.com/tus/tusd/v2 v2.2.2
|
||||||
google.golang.org/api v0.143.0
|
golang.org/x/oauth2 v0.14.0
|
||||||
|
google.golang.org/api v0.152.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.23.0 // indirect
|
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go/compute v1.23.3 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.4.0
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
golang.org/x/crypto v0.13.0 // indirect
|
golang.org/x/crypto v0.15.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||||
google.golang.org/grpc v1.57.0 // indirect
|
google.golang.org/grpc v1.59.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -44,9 +49,9 @@ require (
|
||||||
github.com/subosito/gotenv v1.4.2 // indirect
|
github.com/subosito/gotenv v1.4.2 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.26.0
|
go.uber.org/zap v1.26.0
|
||||||
golang.org/x/net v0.15.0 // indirect
|
golang.org/x/net v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.12.0 // indirect
|
golang.org/x/sys v0.14.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
|
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||||
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
|
@ -40,9 +40,13 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/Acconut/go-httptest-recorder v1.0.0 h1:TAv2dfnqp/l+SUvIaMAUK4GeN4+wqb6KZsFFFTGhoJg=
|
||||||
|
github.com/Acconut/go-httptest-recorder v1.0.0/go.mod h1:CwQyhTH1kq/gLyWiRieo7c0uokpu3PXeyF/nZjUNtmM=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 h1:y4B3+GPxKlrigF1ha5FFErxK+sr6sWxQovRMzwMhejo=
|
||||||
|
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
|
@ -86,6 +90,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
|
@ -116,8 +122,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
|
@ -137,15 +143,13 @@ github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
|
@ -185,8 +189,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||||
|
|
@ -207,10 +211,13 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||||
|
github.com/tus/tusd/v2 v2.2.2 h1:urJ0Is3ew3OWdWF6ZkaTEfJe3c/sFINCkWsK0/73m+k=
|
||||||
|
github.com/tus/tusd/v2 v2.2.2/go.mod h1:XNEk33RSdHtrqVMVKJhoRxw97MYPIbFC78sZWwfqJ0M=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|
@ -236,8 +243,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
|
@ -248,6 +255,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||||
|
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
|
@ -304,8 +313,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
|
@ -315,8 +324,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
|
||||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
|
||||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
|
@ -327,8 +336,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -365,8 +374,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
@ -376,8 +385,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
|
@ -451,8 +460,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513
|
||||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||||
google.golang.org/api v0.143.0 h1:o8cekTkqhywkbZT6p1UHJPZ9+9uuCAJs/KYomxZB8fA=
|
google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY=
|
||||||
google.golang.org/api v0.143.0/go.mod h1:FoX9DO9hT7DLNn97OuoZAGSDuNAXdJRuGK98rSUgurk=
|
google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
|
@ -497,12 +506,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
|
||||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA=
|
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||||
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
|
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI=
|
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
|
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
|
@ -519,8 +528,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,38 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"recipe-manager/config"
|
||||||
|
"recipe-manager/services/oauth"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
s := NewServer()
|
|
||||||
|
config, err := loadConfig(".")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// TODO: use default config instead
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
oauthService := oauth.NewOAuthService(config)
|
||||||
|
|
||||||
|
server := NewServer(config, oauthService)
|
||||||
|
tusServer := NewTusServer(config, oauthService)
|
||||||
|
|
||||||
serverCtx, serverStopCtx := context.WithCancel(context.Background())
|
serverCtx, serverStopCtx := context.WithCancel(context.Background())
|
||||||
|
tusServerCtx, tusServerStopCtx := context.WithCancel(context.Background())
|
||||||
|
|
||||||
sig := make(chan os.Signal, 1)
|
sig := make(chan os.Signal, 1)
|
||||||
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
go func() {
|
go func() {
|
||||||
<-sig
|
<-sig
|
||||||
|
|
||||||
shutdownCtx, cancel := context.WithTimeout(serverCtx, 30*time.Second)
|
shutdownCtx, cancel := context.WithTimeout(serverCtx, 30*time.Second)
|
||||||
|
tusShutdownCtx, tusShutdownCancel := context.WithTimeout(tusServerCtx, 30*time.Second)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-shutdownCtx.Done()
|
<-shutdownCtx.Done()
|
||||||
|
|
@ -29,20 +47,66 @@ func main() {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := s.Shutdown(shutdownCtx)
|
go func() {
|
||||||
|
<-tusShutdownCtx.Done()
|
||||||
|
if errors.Is(tusShutdownCtx.Err(), context.DeadlineExceeded) {
|
||||||
|
log.Println("Tus server shutdown timeout, force exit")
|
||||||
|
tusShutdownCancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := server.Shutdown(shutdownCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = tusServer.Shutdown(tusShutdownCtx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tusServerStopCtx()
|
||||||
serverStopCtx()
|
serverStopCtx()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Spawn a goroutine to run git pull every 10 minutes
|
// Spawn a goroutine to run git pull every 10 minutes
|
||||||
//go RunGitRecipeWorker() // ** Use crontab instead **
|
//go RunGitRecipeWorker() // ** Use crontab instead **
|
||||||
|
|
||||||
err := s.Run()
|
go func() {
|
||||||
if err != nil {
|
err := tusServer.Run()
|
||||||
log.Fatal(err)
|
if err != nil {
|
||||||
}
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := server.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-tusServerCtx.Done()
|
||||||
<-serverCtx.Done()
|
<-serverCtx.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadConfig(path string) (*config.ServerConfig, error) {
|
||||||
|
viper.AddConfigPath(path)
|
||||||
|
viper.SetConfigName("app")
|
||||||
|
viper.SetConfigType("env")
|
||||||
|
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
|
var serverConfig config.ServerConfig
|
||||||
|
err := viper.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = viper.Unmarshal(&serverConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &serverConfig, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,31 +23,9 @@ import (
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadConfig(path string) (*config.ServerConfig, error) {
|
|
||||||
viper.AddConfigPath(path)
|
|
||||||
viper.SetConfigName("app")
|
|
||||||
viper.SetConfigType("env")
|
|
||||||
|
|
||||||
viper.AutomaticEnv()
|
|
||||||
|
|
||||||
var serverConfig config.ServerConfig
|
|
||||||
err := viper.ReadInConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = viper.Unmarshal(&serverConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &serverConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
server *http.Server
|
server *http.Server
|
||||||
data *data.Data
|
data *data.Data
|
||||||
|
|
@ -57,22 +35,17 @@ type Server struct {
|
||||||
taoLogger *logger.TaoLogger
|
taoLogger *logger.TaoLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer() *Server {
|
func NewServer(cfg *config.ServerConfig, oauthService oauth.OAuthService) *Server {
|
||||||
|
|
||||||
serverCfg, err := loadConfig(".")
|
taoLogger := logger.NewTaoLogger(cfg)
|
||||||
|
taoLogger.Log = taoLogger.Log.Named("Server")
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
taoLogger := logger.NewTaoLogger(serverCfg)
|
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
server: &http.Server{Addr: fmt.Sprintf(":%d", serverCfg.ServerPort)},
|
server: &http.Server{Addr: fmt.Sprintf(":%d", cfg.ServerPort)},
|
||||||
data: data.NewData(taoLogger),
|
data: data.NewData(taoLogger),
|
||||||
database: data.NewSqliteDatabase(),
|
database: data.NewSqliteDatabase(),
|
||||||
cfg: serverCfg,
|
cfg: cfg,
|
||||||
oauth: oauth.NewOAuthService(serverCfg),
|
oauth: oauthService,
|
||||||
taoLogger: taoLogger,
|
taoLogger: taoLogger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
135
server/tus_server.go
Normal file
135
server/tus_server.go
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"recipe-manager/config"
|
||||||
|
"recipe-manager/data"
|
||||||
|
"recipe-manager/helpers"
|
||||||
|
"recipe-manager/services/logger"
|
||||||
|
"recipe-manager/services/oauth"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/tus/tusd/v2/pkg/filestore"
|
||||||
|
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TusServer struct {
|
||||||
|
cfg *config.ServerConfig
|
||||||
|
oauth oauth.OAuthService
|
||||||
|
server *http.Server
|
||||||
|
handler *tusd.Handler
|
||||||
|
taoLogger *logger.TaoLogger
|
||||||
|
database *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTusServer(cfg *config.ServerConfig, oauth oauth.OAuthService) *TusServer {
|
||||||
|
|
||||||
|
taoLogger := logger.NewTaoLogger(cfg)
|
||||||
|
taoLogger.Log = taoLogger.Log.Named("TusServer")
|
||||||
|
|
||||||
|
store := filestore.FileStore{
|
||||||
|
Path: "./uploads",
|
||||||
|
}
|
||||||
|
|
||||||
|
composer := tusd.NewStoreComposer()
|
||||||
|
store.UseIn(composer)
|
||||||
|
|
||||||
|
handler, err := tusd.NewHandler(tusd.Config{
|
||||||
|
BasePath: "/files/",
|
||||||
|
StoreComposer: composer,
|
||||||
|
NotifyCompleteUploads: true,
|
||||||
|
Cors: &tusd.CorsConfig{
|
||||||
|
AllowOrigin: regexp.MustCompile(strings.Replace(cfg.AllowedOrigins, ",", "|", -1)),
|
||||||
|
AllowCredentials: true,
|
||||||
|
AllowMethods: tusd.DefaultCorsConfig.AllowMethods,
|
||||||
|
AllowHeaders: tusd.DefaultCorsConfig.AllowHeaders,
|
||||||
|
ExposeHeaders: tusd.DefaultCorsConfig.ExposeHeaders,
|
||||||
|
MaxAge: tusd.DefaultCorsConfig.MaxAge,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("unable to create handler: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
event := <-handler.CompleteUploads
|
||||||
|
fmt.Printf("Upload %s finished\n", event.Upload.ID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return &TusServer{
|
||||||
|
server: &http.Server{Addr: ":" + strconv.Itoa(int(cfg.TusServerPort))},
|
||||||
|
cfg: cfg,
|
||||||
|
handler: handler,
|
||||||
|
taoLogger: taoLogger,
|
||||||
|
oauth: oauth,
|
||||||
|
database: data.NewSqliteDatabase(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TusServer) Run() error {
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
ts.taoLogger.Log.Info("Server running", zap.String("addr", ts.server.Addr))
|
||||||
|
|
||||||
|
// User Service
|
||||||
|
// userService := user.NewUserService(ts.cfg, ts.database, ts.taoLogger)
|
||||||
|
|
||||||
|
mux.Handle("/files/", http.StripPrefix("/files/", ts.handler))
|
||||||
|
|
||||||
|
mux.HandleFunc("/list", listFiles)
|
||||||
|
|
||||||
|
port := strconv.Itoa(int(ts.cfg.TusServerPort))
|
||||||
|
|
||||||
|
if port == "" {
|
||||||
|
port = "8090"
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.ListenAndServe(":"+strconv.Itoa(int(ts.cfg.TusServerPort)), mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TusServer) Shutdown(ctx context.Context) error {
|
||||||
|
return ts.server.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// read all file in ./uploads/*.info file
|
||||||
|
// unmarshal json and return the list of files
|
||||||
|
|
||||||
|
// add cors
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
files := helpers.ListFile("./uploads/*.info")
|
||||||
|
|
||||||
|
var result []interface{}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
fileContent, err := helpers.ReadFile(file)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var data interface{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(fileContent), &data); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(result)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue