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 = {
|
||||
root: true,
|
||||
env: {
|
||||
"es2021": true,
|
||||
"node": true,
|
||||
"browser": false
|
||||
es2021: true,
|
||||
node: true,
|
||||
browser: false
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
|
|
@ -11,23 +11,16 @@ module.exports = {
|
|||
'plugin:react-hooks/recommended',
|
||||
'prettier'
|
||||
],
|
||||
ignorePatterns: [
|
||||
"types/env.d.ts",
|
||||
"node_modules/**",
|
||||
"**/dist/**",
|
||||
],
|
||||
ignorePatterns: ['types/env.d.ts', 'node_modules/**', '**/dist/**', 'scr/components/ui/**'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: 'module',
|
||||
sourceType: 'module'
|
||||
},
|
||||
plugins: ['react-refresh', '@typescript-eslint', '@tanstack/eslint-plugin-query'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
},
|
||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||
'@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
|
||||
|
||||
- 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'),
|
||||
permissions: params.get('permissions'),
|
||||
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')
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
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)
|
||||
})
|
||||
|
||||
icpMain.on('delete-keyChain', (_event, serviceName, account) => {
|
||||
setPassword(serviceName, account, '')
|
||||
icpMain.on('delete-keyChain', (_event, { serviceName, account }) => {
|
||||
deletePassword(serviceName, account)
|
||||
})
|
||||
|
||||
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: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",
|
||||
"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": {
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
|
|
@ -35,6 +36,14 @@
|
|||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@tanstack/react-query": "^5.17.19",
|
||||
"@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-credential-web": "^0.0.22",
|
||||
"@yume-chan/adb-daemon-webusb": "^0.0.22",
|
||||
|
|
@ -49,6 +58,7 @@
|
|||
"next-themes": "^0.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hook-form": "^7.49.3",
|
||||
"react-router-dom": "^6.21.1",
|
||||
"sonner": "^1.3.1",
|
||||
|
|
|
|||
|
|
@ -1,17 +1,43 @@
|
|||
import { createBrowserRouter, createHashRouter, RouterProvider, type RouteObject } from 'react-router-dom'
|
||||
import AuthCallBack from './AuthCallBack'
|
||||
import MainLayout from './layouts/MainLayout'
|
||||
import HomePage from './pages/Home'
|
||||
import LoginPage from './pages/Login'
|
||||
import AndroidPage from './pages/Android'
|
||||
import HomePage from './pages/home'
|
||||
import LoginPage from './pages/login'
|
||||
import AndroidPage from './pages/android'
|
||||
import RecipesPage from './pages/recipes/recipes'
|
||||
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() {
|
||||
const routes: RouteObject[] = [
|
||||
{
|
||||
path: '/',
|
||||
element: <MainLayout />,
|
||||
element: <MainLayout sidebarMenu={sideBarMenuList} />,
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
|
|
@ -24,6 +50,10 @@ function router() {
|
|||
{
|
||||
path: 'android',
|
||||
element: <AndroidPage />
|
||||
},
|
||||
{
|
||||
path: 'upload',
|
||||
element: <UploadPage />
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,23 +5,27 @@ const AuthCallBack: React.FC = () => {
|
|||
const params = new URLSearchParams(window.location.search)
|
||||
// emit message to main process
|
||||
|
||||
window.opener.postMessage(
|
||||
{
|
||||
payload: 'loginSuccess',
|
||||
data: {
|
||||
id: params.get('id'),
|
||||
name: params.get('name'),
|
||||
email: params.get('email'),
|
||||
picture: params.get('picture'),
|
||||
permissions: params.get('permissions')
|
||||
}
|
||||
},
|
||||
'*'
|
||||
)
|
||||
if (params.get('kind') === 'electron') {
|
||||
window.location.href = import.meta.env.TAOBIN_RECIPE_MANAGER_DEEPLINK_PROTOCOL + '/login' + window.location.search
|
||||
} else {
|
||||
window.opener.postMessage(
|
||||
{
|
||||
payload: 'loginSuccess',
|
||||
data: {
|
||||
id: params.get('id'),
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,29 +1,32 @@
|
|||
import { DashboardIcon, FileTextIcon, RocketIcon } from '@radix-ui/react-icons'
|
||||
import React from 'react'
|
||||
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 (
|
||||
<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">
|
||||
<ul className="space-y-2 font-medium">
|
||||
<li>
|
||||
<Link to="/" className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500">
|
||||
<DashboardIcon className="inline-block w-6 h-6 mr-3" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/recipes" className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500">
|
||||
<FileTextIcon className="inline-block w-6 h-6 mr-3" />
|
||||
Recipes
|
||||
</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>
|
||||
{menuList.map((item, index) => (
|
||||
<li key={index}>
|
||||
<Link
|
||||
to={item.link}
|
||||
className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500"
|
||||
>
|
||||
{<item.icon className="inline-block w-6 h-6 mr-3" />}
|
||||
{item.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</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 customAxios from '../lib/customAxios'
|
||||
|
||||
export interface UserInfo {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
picture: string
|
||||
permissions: string[]
|
||||
}
|
||||
import type { User } from '@/models/user/schema'
|
||||
|
||||
interface UserAuth {
|
||||
userInfo: UserInfo | null
|
||||
setUserInfo: (userInfo: UserInfo | null) => void
|
||||
getUserInfo: () => Promise<UserInfo | null>
|
||||
userInfo: User | null
|
||||
setUserInfo: (userInfo: User | null) => void
|
||||
getUserInfo: () => Promise<User | null>
|
||||
logout: () => void
|
||||
}
|
||||
|
||||
|
|
@ -21,7 +14,7 @@ const userAuthStore = create<UserAuth>(set => ({
|
|||
setUserInfo: userInfo => set({ userInfo }),
|
||||
getUserInfo: () => {
|
||||
return customAxios
|
||||
.get<UserInfo>('/user/me')
|
||||
.get<User>('/user/me')
|
||||
.then(res => {
|
||||
set({ userInfo: res.data })
|
||||
return res.data
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
import { Outlet, useNavigate } from 'react-router-dom'
|
||||
import Navbar from '../components/Header'
|
||||
import Sidebar from '../components/Sidebar'
|
||||
import Navbar from '../components/header'
|
||||
import Sidebar, { type MenuList } from '../components/sidebar'
|
||||
import userAuthStore from '../hooks/userAuth'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useCallback } from 'react'
|
||||
import { Toaster } from '@/components/ui/toaster'
|
||||
|
||||
const MainLayout = () => {
|
||||
interface MainLayoutProps {
|
||||
sidebarMenu: MenuList
|
||||
}
|
||||
|
||||
const MainLayout: React.FC<MainLayoutProps> = ({ sidebarMenu }) => {
|
||||
const { userInfo, getUserInfo } = userAuthStore(
|
||||
useShallow(state => ({
|
||||
userInfo: state.userInfo,
|
||||
|
|
@ -33,7 +37,7 @@ const MainLayout = () => {
|
|||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<Sidebar />
|
||||
<Sidebar menuList={sidebarMenu} />
|
||||
<main className="p-8 sm:ml-64 mt-20">
|
||||
<Outlet />
|
||||
<Toaster />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import axios from 'axios'
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
|
|
|
|||
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 { twMerge } from "tailwind-merge"
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ if (window.electronRuntime) {
|
|||
})
|
||||
|
||||
window.ipcRenderer
|
||||
.invoke(
|
||||
'keyChainSync',
|
||||
import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME
|
||||
)
|
||||
.invoke('keyChainSync', import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME)
|
||||
.then(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() {
|
||||
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(
|
||||
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(
|
||||
new WritableStream({
|
||||
|
|
@ -111,7 +115,12 @@ const AndroidPage: React.FC = () => {
|
|||
control: true,
|
||||
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
|
||||
|
||||
|
|
@ -241,7 +250,7 @@ const AndroidPage: React.FC = () => {
|
|||
}
|
||||
|
||||
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>
|
||||
<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 googleLogo from '../assets/google-color.svg'
|
||||
import userAuthStore, { type UserInfo } from '../hooks/userAuth'
|
||||
import googleLogo from '@/assets/google-color.svg'
|
||||
import userAuthStore from '@/hooks/userAuth'
|
||||
import { userSchema } from '@/models/user/schema'
|
||||
|
||||
const LoginPage: React.FC = () => {
|
||||
const setUserInfo = userAuthStore(state => state.setUserInfo)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const redirectUrl =
|
||||
new URLSearchParams(window.location.search).get('redirect_to') ?? '/'
|
||||
const redirectUrl = new URLSearchParams(window.location.search).get('redirect_to') ?? '/'
|
||||
|
||||
const loginWithGoogle = () => {
|
||||
// if is web mode then use window.open
|
||||
|
|
@ -15,24 +15,35 @@ const LoginPage: React.FC = () => {
|
|||
if (window.electronRuntime) {
|
||||
window.ipcRenderer.send(
|
||||
'deeplink',
|
||||
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL +
|
||||
'/auth/google?redirect_to=' +
|
||||
redirectUrl +
|
||||
'&kind=electron'
|
||||
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL + '/auth/google?redirect_to=' + redirectUrl + '&kind=electron'
|
||||
)
|
||||
|
||||
window.ipcRenderer.on('loginSuccess', (_event, 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)
|
||||
})
|
||||
} else {
|
||||
// open new window and listen to message from window.opener
|
||||
window.open(
|
||||
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL +
|
||||
'/auth/google?redirect_to=' +
|
||||
redirectUrl,
|
||||
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL + '/auth/google?redirect_to=' + redirectUrl,
|
||||
'_blank',
|
||||
'width=500,height=600'
|
||||
)
|
||||
|
|
@ -40,22 +51,8 @@ const LoginPage: React.FC = () => {
|
|||
// listen to message from new window
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data.payload === 'loginSuccess') {
|
||||
// const { access_token, max_age, refresh_token } = event.data.data
|
||||
// setPassword(
|
||||
// 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)
|
||||
const user = userSchema.parse(event.data.data)
|
||||
setUserInfo(user)
|
||||
|
||||
navigate(redirectUrl)
|
||||
}
|
||||
|
|
@ -64,17 +61,12 @@ const LoginPage: React.FC = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-screen">
|
||||
<div className="flex h-screen flex-col items-center justify-center">
|
||||
<button
|
||||
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
|
||||
className="w-6 h-6"
|
||||
src={googleLogo}
|
||||
alt="google logo"
|
||||
loading="eager"
|
||||
/>
|
||||
<img className="h-6 w-6" src={googleLogo} alt="google logo" loading="eager" />
|
||||
<span>Login with @forth.co.th Google account</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
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 DataTableColumnHeader from './data-table-column-header'
|
||||
import { labels, priorities, statuses } from '../models/data'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
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',
|
||||
header: ({ table }) => (
|
||||
|
|
@ -29,22 +29,22 @@ export const columns: ColumnDef<Task>[] = [
|
|||
enableHiding: false
|
||||
},
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Task" />,
|
||||
cell: ({ row }) => <div className="w-[80px]">{row.getValue('id')}</div>,
|
||||
accessorKey: 'productCode',
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} title="ProductCode" />,
|
||||
cell: ({ row }) => <div className="w-[80px]">{row.getValue('productCode')}</div>,
|
||||
enableSorting: false,
|
||||
enableHiding: false
|
||||
},
|
||||
{
|
||||
accessorKey: 'title',
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Title" />,
|
||||
accessorKey: 'name',
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Name" />,
|
||||
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 (
|
||||
<div className="flex space-x-2">
|
||||
{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>
|
||||
)
|
||||
}
|
||||
|
|
@ -53,16 +53,16 @@ export const columns: ColumnDef<Task>[] = [
|
|||
accessorKey: 'status',
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Status" />,
|
||||
cell: ({ row }) => {
|
||||
const status = statuses.find(status => status.value === row.getValue('status'))
|
||||
|
||||
const status: RecipeStatus = row.getValue('status')
|
||||
const StatusIcon = getRecipeStatusIcon(status)
|
||||
if (!status) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-[100px] items-center">
|
||||
{status.icon && <status.icon className="mr-2 h-4 w-4 text-muted-foreground" />}
|
||||
<span>{status.label}</span>
|
||||
{<StatusIcon className="text-muted-foreground mr-2 h-4 w-4" />}
|
||||
<span>{}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
|
@ -71,19 +71,12 @@ export const columns: ColumnDef<Task>[] = [
|
|||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'priority',
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Priority" />,
|
||||
accessorKey: 'lastUpdated',
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Last Updated" />,
|
||||
cell: ({ row }) => {
|
||||
const priority = priorities.find(priority => priority.value === row.getValue('priority'))
|
||||
|
||||
if (!priority) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{priority.icon && <priority.icon className="mr-2 h-4 w-4 text-muted-foreground" />}
|
||||
<span>{priority.label}</span>
|
||||
<span>{row.getValue('lastUpdated')}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { type Row } from '@tanstack/react-table'
|
||||
import { taskSchema } from '../models/schema'
|
||||
import { taskSchema } from '@/models/recipe/schema'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from '@/components/ui/dropdown-menu'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DotsHorizontalIcon } from '@radix-ui/react-icons'
|
||||
import { labels } from '../models/data'
|
||||
import { labels } from '@/models/data'
|
||||
|
||||
interface DataTableRowActionsProps<TData> {
|
||||
row: Row<TData>
|
||||
|
|
@ -27,7 +27,7 @@ const DataTableRowActions = <TData,>({ row }: DataTableRowActionsProps<TData>) =
|
|||
return (
|
||||
<DropdownMenu>
|
||||
<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" />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
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 DataTableViewOptions from './data-table-view-options'
|
||||
import { Cross2Icon } from '@radix-ui/react-icons'
|
||||
|
|
@ -15,7 +15,7 @@ const DataTableToolbar = <TData,>({ table }: DataTableToolbarProps<TData>) => {
|
|||
|
||||
return (
|
||||
<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 flex-1 items-center space-x-2">
|
||||
<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'
|
||||
|
||||
// Simulate a database read for tasks.
|
||||
async function getTasks() {
|
||||
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)
|
||||
}
|
||||
import { columns } from './components/columns'
|
||||
import DataTable from './components/data-table'
|
||||
import { getRecipeOverview } from '@/hooks/recipe/get-recipe-overview'
|
||||
|
||||
const RecipesPage = () => {
|
||||
const { data: tasks, isLoading } = useQuery({ queryKey: ['tasks'], queryFn: getTasks })
|
||||
|
||||
console.log('here')
|
||||
const { data: recipeOverviewList, isLoading } = useQuery({
|
||||
queryKey: ['recipe-overview'],
|
||||
queryFn: () => getRecipeOverview()
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-3">
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<section>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Recipes</h1>
|
||||
</section>
|
||||
<section>
|
||||
<DataTable data={tasks} columns={columns} isLoading={isLoading} />
|
||||
<DataTable data={recipeOverviewList ?? []} columns={columns} isLoading={isLoading} />
|
||||
</section>
|
||||
</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 {
|
||||
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_ACCOUNT_ACCESS_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
|
||||
client_secret.json
|
||||
app.env
|
||||
cofffeemachineConfig
|
||||
cofffeemachineConfig
|
||||
/uploads/*
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 85d4ada5c95a77803281ef76b1421807d9203353
|
||||
Subproject commit 6e0d512afc4667a2204ff773ad8a3851120cdadd
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
package config
|
||||
|
||||
type ServerConfig struct {
|
||||
ServerPort uint `mapstructure:"SERVER_PORT"`
|
||||
ServerPort int `mapstructure:"SERVER_PORT"`
|
||||
TusServerPort int `mapstructure:"TUS_SERVER_PORT"`
|
||||
AllowedOrigins string `mapstructure:"ALLOWED_ORIGINS"`
|
||||
ClientRedirectURL string `mapstructure:"CLIENT_REDIRECT_URL"`
|
||||
ClientElectronRedirectURL string `mapstructure:"CLIENT_ELECTRON_REDIRECT_URL"`
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -5,26 +5,31 @@ go 1.21.1
|
|||
require (
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/mattn/go-sqlite3 v1.14.18
|
||||
github.com/pkg/errors v0.9.1
|
||||
golang.org/x/oauth2 v0.12.0
|
||||
google.golang.org/api v0.143.0
|
||||
github.com/tus/tusd/v2 v2.2.2
|
||||
golang.org/x/oauth2 v0.14.0
|
||||
google.golang.org/api v0.152.0
|
||||
)
|
||||
|
||||
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
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
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
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.13.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
||||
google.golang.org/grpc v1.57.0 // indirect
|
||||
golang.org/x/crypto v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
)
|
||||
|
||||
|
|
@ -44,9 +49,9 @@ require (
|
|||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.31.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.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/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
|
||||
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
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/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
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.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
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/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
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.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.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.3.1/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.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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
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/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=
|
||||
|
|
@ -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.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
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.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
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.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/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/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.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
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/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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
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/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
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.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.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
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/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.27/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-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.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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-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-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-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
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-20190226205417-e64efc72b421/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-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.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
||||
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
|
||||
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-20181108010431-42b317875d0f/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-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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-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.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
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/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=
|
||||
|
|
@ -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.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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
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-20190308202827-9d24e82272b4/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.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
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.143.0/go.mod h1:FoX9DO9hT7DLNn97OuoZAGSDuNAXdJRuGK98rSUgurk=
|
||||
google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY=
|
||||
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.4.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-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-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA=
|
||||
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
|
||||
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-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
|
||||
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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
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.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.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
|
|
|||
|
|
@ -6,20 +6,38 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"recipe-manager/config"
|
||||
"recipe-manager/services/oauth"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
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())
|
||||
tusServerCtx, tusServerStopCtx := context.WithCancel(context.Background())
|
||||
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
go func() {
|
||||
<-sig
|
||||
|
||||
shutdownCtx, cancel := context.WithTimeout(serverCtx, 30*time.Second)
|
||||
tusShutdownCtx, tusShutdownCancel := context.WithTimeout(tusServerCtx, 30*time.Second)
|
||||
|
||||
go func() {
|
||||
<-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 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = tusServer.Shutdown(tusShutdownCtx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
tusServerStopCtx()
|
||||
serverStopCtx()
|
||||
}()
|
||||
|
||||
// Spawn a goroutine to run git pull every 10 minutes
|
||||
//go RunGitRecipeWorker() // ** Use crontab instead **
|
||||
|
||||
err := s.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
go func() {
|
||||
err := tusServer.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := server.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
<-tusServerCtx.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/cors"
|
||||
"github.com/spf13/viper"
|
||||
"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 {
|
||||
server *http.Server
|
||||
data *data.Data
|
||||
|
|
@ -57,22 +35,17 @@ type Server struct {
|
|||
taoLogger *logger.TaoLogger
|
||||
}
|
||||
|
||||
func NewServer() *Server {
|
||||
func NewServer(cfg *config.ServerConfig, oauthService oauth.OAuthService) *Server {
|
||||
|
||||
serverCfg, err := loadConfig(".")
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
taoLogger := logger.NewTaoLogger(serverCfg)
|
||||
taoLogger := logger.NewTaoLogger(cfg)
|
||||
taoLogger.Log = taoLogger.Log.Named("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),
|
||||
database: data.NewSqliteDatabase(),
|
||||
cfg: serverCfg,
|
||||
oauth: oauth.NewOAuthService(serverCfg),
|
||||
cfg: cfg,
|
||||
oauth: oauthService,
|
||||
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