Compare commits
19 Commits
main
...
ded24e4ad5
| Author | SHA1 | Date | |
|---|---|---|---|
| ded24e4ad5 | |||
| e5015dd625 | |||
| fa60dc14a8 | |||
| 91be4eb7f6 | |||
| ab87802fcf | |||
| 7b5a5b9789 | |||
| bec8f680eb | |||
|
fd4da4f1a1
|
|||
|
485d0de64b
|
|||
|
b933522123
|
|||
|
2d92d5fba7
|
|||
|
d314942c08
|
|||
|
1505783c62
|
|||
|
f130401ff8
|
|||
|
0fb5c8b758
|
|||
|
dc128c0392
|
|||
|
e2345a8d4a
|
|||
|
55e7d3520a
|
|||
|
b81a43019a
|
9
.env.development
Normal file
9
.env.development
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
SERVER_ADDRESS=:8000
|
||||||
|
SERVER_DEBUG_MODE=true
|
||||||
|
SERVER_FILE_LOGGER=false
|
||||||
|
SERVER_JWT_SECRET=test
|
||||||
|
DATABASE_TYPE=postgres
|
||||||
|
DATABASE_HOST=127.0.0.1
|
||||||
|
DATABASE_NAME=postgres
|
||||||
|
DATABASE_USERNAME=postgres
|
||||||
|
DATABASE_PASSWORD=postgres
|
||||||
1
.env.production
Normal file
1
.env.production
Normal file
@@ -0,0 +1 @@
|
|||||||
|
TZ=Asia/Shanghai
|
||||||
12
.envrc
Normal file
12
.envrc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
export DIRENV_WARN_TIMEOUT=20s
|
||||||
|
|
||||||
|
eval "$(devenv direnvrc)"
|
||||||
|
|
||||||
|
# `use devenv` supports the same options as the `devenv shell` command.
|
||||||
|
#
|
||||||
|
# To silence all output, use `--quiet`.
|
||||||
|
#
|
||||||
|
# Example usage: use devenv --quiet --impure --option services.postgres.enable:bool true
|
||||||
|
use devenv
|
||||||
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# devenv
|
||||||
|
.devenv*
|
||||||
|
devenv.local.nix
|
||||||
|
devenv.local.yaml
|
||||||
|
|
||||||
|
# direnv
|
||||||
|
.direnv
|
||||||
|
|
||||||
|
# pre-commit
|
||||||
|
.pre-commit-config.yaml
|
||||||
|
|
||||||
|
# build files
|
||||||
|
.outputs/
|
||||||
|
|
||||||
|
# go binaries
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# test binary
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# profiles and artifacts
|
||||||
|
*.out
|
||||||
|
coverage.*
|
||||||
|
*.coverprofile
|
||||||
|
profile.cov
|
||||||
|
|
||||||
|
# dependency directories
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
# go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# env file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# editor/ide
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# apple crap
|
||||||
|
.DS_Store
|
||||||
|
__MACOSX
|
||||||
|
._*
|
||||||
35
.zed/settings.json
Normal file
35
.zed/settings.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Folder-specific settings
|
||||||
|
//
|
||||||
|
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||||
|
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
||||||
|
|
||||||
|
{
|
||||||
|
"tab_size": 4,
|
||||||
|
"format_on_save": "on",
|
||||||
|
"languages": {
|
||||||
|
"TypeScript": {
|
||||||
|
"language_servers": [
|
||||||
|
"typescript-language-server",
|
||||||
|
"!vtsls",
|
||||||
|
"!deno",
|
||||||
|
"...",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"TSX": {
|
||||||
|
"language_servers": [
|
||||||
|
"typescript-language-server",
|
||||||
|
"!vtsls",
|
||||||
|
"!deno",
|
||||||
|
"...",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"JavaScript": {
|
||||||
|
"language_servers": [
|
||||||
|
"typescript-language-server",
|
||||||
|
"!vtsls",
|
||||||
|
"!deno",
|
||||||
|
"...",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
1
client/.envrc
Normal file
1
client/.envrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
use flake . --impure
|
||||||
26
client/.gitignore
vendored
Normal file
26
client/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
.direnv
|
||||||
1209
client/bun.lock
Normal file
1209
client/bun.lock
Normal file
File diff suppressed because it is too large
Load Diff
22
client/components.json
Normal file
22
client/components.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "",
|
||||||
|
"css": "src/index.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide",
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"registries": {}
|
||||||
|
}
|
||||||
16
client/eslint.config.js
Normal file
16
client/eslint.config.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import antfu from '@antfu/eslint-config';
|
||||||
|
import pluginQuery from '@tanstack/eslint-plugin-query';
|
||||||
|
|
||||||
|
export default antfu({
|
||||||
|
gitignore: true,
|
||||||
|
ignores: ['**/node_modules/**', '**/dist/**', 'bun.lock', '**/routeTree.gen.ts'],
|
||||||
|
react: true,
|
||||||
|
stylistic: {
|
||||||
|
semi: true,
|
||||||
|
quotes: 'single',
|
||||||
|
indent: 2,
|
||||||
|
},
|
||||||
|
typescript: {
|
||||||
|
tsconfigPath: 'tsconfig.json',
|
||||||
|
},
|
||||||
|
}, ...pluginQuery.configs['flat/recommended']);
|
||||||
61
client/flake.lock
generated
Normal file
61
client/flake.lock
generated
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1765779637,
|
||||||
|
"narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "1306659b587dc277866c7b69eb97e5f07864d8c4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
28
client/flake.nix
Normal file
28
client/flake.nix
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
description = "Basic flake for devShell";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
bun
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
13
client/index.html
Normal file
13
client/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>client</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
53
client/package.json
Normal file
53
client/package.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "client",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"@tanstack/react-query": "^5.90.12",
|
||||||
|
"@tanstack/react-router": "^1.141.6",
|
||||||
|
"@tanstack/react-router-devtools": "^1.141.6",
|
||||||
|
"axios": "^1.13.2",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.562.0",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"tailwind-merge": "^3.4.0",
|
||||||
|
"tailwindcss": "^4.1.18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@antfu/eslint-config": "^6.7.1",
|
||||||
|
"@eslint-react/eslint-plugin": "^2.3.13",
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
|
"@tanstack/eslint-plugin-query": "^5.91.2",
|
||||||
|
"@tanstack/router-plugin": "^1.141.7",
|
||||||
|
"@types/node": "^25.0.3",
|
||||||
|
"@types/react": "^19.2.5",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
|
"eslint": "^9.39.1",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.26",
|
||||||
|
"globals": "^16.5.0",
|
||||||
|
"lint-staged": "^16.2.7",
|
||||||
|
"simple-git-hooks": "^2.13.1",
|
||||||
|
"tw-animate-css": "^1.4.0",
|
||||||
|
"typescript": "~5.9.3",
|
||||||
|
"typescript-eslint": "^8.46.4",
|
||||||
|
"vite": "^7.2.4"
|
||||||
|
},
|
||||||
|
"simple-git-hooks": {
|
||||||
|
"pre-commit": "bun run lint-staged"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*": "eslint --fix"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
client/src/App.tsx
Normal file
11
client/src/App.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { ThemeProvider } from '@/components/theme-provider';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||||
|
<p>Hello world</p>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { App };
|
||||||
17
client/src/components/Time.tsx
Normal file
17
client/src/components/Time.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export function Time() {
|
||||||
|
const { data: time } = useSuspenseQuery({
|
||||||
|
queryKey: ['time'],
|
||||||
|
queryFn: async () => axios.get<{ datetime: string }>('https://worldtimeapi.org/api/timezone/Asia/Shanghai')
|
||||||
|
.then(res => res.data.datetime)
|
||||||
|
.then(isoString => new Date(isoString).toLocaleTimeString()),
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
Current time:
|
||||||
|
{time}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
client/src/components/theme-provider.tsx
Normal file
53
client/src/components/theme-provider.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import type { Theme } from '@/hooks/useTheme';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { ThemeProviderContext } from '@/hooks/useTheme';
|
||||||
|
|
||||||
|
interface ThemeProviderProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
defaultTheme?: Theme;
|
||||||
|
storageKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ThemeProvider({
|
||||||
|
children,
|
||||||
|
defaultTheme = 'dark',
|
||||||
|
storageKey = 'vite-ui-theme',
|
||||||
|
...props
|
||||||
|
}: ThemeProviderProps) {
|
||||||
|
const [theme, setTheme] = useState<Theme>(
|
||||||
|
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = window.document.documentElement;
|
||||||
|
|
||||||
|
root.classList.remove('light', 'dark');
|
||||||
|
|
||||||
|
if (theme === 'system') {
|
||||||
|
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
.matches
|
||||||
|
? 'dark'
|
||||||
|
: 'light';
|
||||||
|
|
||||||
|
root.classList.add(systemTheme);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.classList.add(theme);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/no-unstable-context-value
|
||||||
|
const value = {
|
||||||
|
theme,
|
||||||
|
setTheme: (theme: Theme) => {
|
||||||
|
localStorage.setItem(storageKey, theme);
|
||||||
|
setTheme(theme);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProviderContext {...props} value={value}>
|
||||||
|
{children}
|
||||||
|
</ThemeProviderContext>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
client/src/hooks/useTheme.ts
Normal file
24
client/src/hooks/useTheme.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { createContext, use } from 'react';
|
||||||
|
|
||||||
|
export type Theme = 'dark' | 'light' | 'system';
|
||||||
|
|
||||||
|
interface ThemeProviderState {
|
||||||
|
theme: Theme;
|
||||||
|
setTheme: (theme: Theme) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: ThemeProviderState = {
|
||||||
|
theme: 'system',
|
||||||
|
setTheme: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const context = use(ThemeProviderContext);
|
||||||
|
|
||||||
|
if (context === undefined)
|
||||||
|
throw new Error('useTheme must be used within a ThemeProvider');
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
193
client/src/index.css
Normal file
193
client/src/index.css
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
--radius-2xl: calc(var(--radius) + 8px);
|
||||||
|
--radius-3xl: calc(var(--radius) + 12px);
|
||||||
|
--radius-4xl: calc(var(--radius) + 16px);
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--color-sidebar: var(--sidebar);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
--font-sans: 'Inter', sans-serif;
|
||||||
|
--font-mono: 'JetBrains Mono', monospace;
|
||||||
|
--font-serif: 'Lora', serif;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--tracking-tighter: calc(var(--tracking-normal) - 0.05em);
|
||||||
|
--tracking-tight: calc(var(--tracking-normal) - 0.025em);
|
||||||
|
--tracking-wide: calc(var(--tracking-normal) + 0.025em);
|
||||||
|
--tracking-wider: calc(var(--tracking-normal) + 0.05em);
|
||||||
|
--tracking-widest: calc(var(--tracking-normal) + 0.1em);
|
||||||
|
--tracking-normal: var(--tracking-normal);
|
||||||
|
--shadow-2xl: var(--shadow-2xl);
|
||||||
|
--shadow-xl: var(--shadow-xl);
|
||||||
|
--shadow-lg: var(--shadow-lg);
|
||||||
|
--shadow-md: var(--shadow-md);
|
||||||
|
--shadow: var(--shadow);
|
||||||
|
--shadow-sm: var(--shadow-sm);
|
||||||
|
--shadow-xs: var(--shadow-xs);
|
||||||
|
--shadow-2xs: var(--shadow-2xs);
|
||||||
|
--spacing: var(--spacing);
|
||||||
|
--letter-spacing: var(--letter-spacing);
|
||||||
|
--shadow-offset-y: var(--shadow-offset-y);
|
||||||
|
--shadow-offset-x: var(--shadow-offset-x);
|
||||||
|
--shadow-spread: var(--shadow-spread);
|
||||||
|
--shadow-blur: var(--shadow-blur);
|
||||||
|
--shadow-opacity: var(--shadow-opacity);
|
||||||
|
--color-shadow-color: var(--shadow-color);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: oklch(0.9816 0.0017 247.8390);
|
||||||
|
--foreground: oklch(0.2621 0.0095 248.1897);
|
||||||
|
--card: oklch(1.0000 0 0);
|
||||||
|
--card-foreground: oklch(0.2621 0.0095 248.1897);
|
||||||
|
--popover: oklch(1.0000 0 0);
|
||||||
|
--popover-foreground: oklch(0.2621 0.0095 248.1897);
|
||||||
|
--primary: oklch(0.5502 0.1193 263.8209);
|
||||||
|
--primary-foreground: oklch(1.0000 0 0);
|
||||||
|
--secondary: oklch(0.7499 0.0898 239.3977);
|
||||||
|
--secondary-foreground: oklch(0.2621 0.0095 248.1897);
|
||||||
|
--muted: oklch(0.9417 0.0052 247.8790);
|
||||||
|
--muted-foreground: oklch(0.5575 0.0165 244.8933);
|
||||||
|
--accent: oklch(0.9417 0.0052 247.8790);
|
||||||
|
--accent-foreground: oklch(0.2621 0.0095 248.1897);
|
||||||
|
--destructive: oklch(0.5915 0.2020 21.2388);
|
||||||
|
--border: oklch(0.9109 0.0070 247.9014);
|
||||||
|
--input: oklch(1.0000 0 0);
|
||||||
|
--ring: oklch(0.5502 0.1193 263.8209);
|
||||||
|
--chart-1: oklch(0.5502 0.1193 263.8209);
|
||||||
|
--chart-2: oklch(0.7499 0.0898 239.3977);
|
||||||
|
--chart-3: oklch(0.4711 0.0998 264.0792);
|
||||||
|
--chart-4: oklch(0.6689 0.0699 240.3096);
|
||||||
|
--chart-5: oklch(0.5107 0.1098 263.6921);
|
||||||
|
--sidebar: oklch(0.9632 0.0034 247.8585);
|
||||||
|
--sidebar-foreground: oklch(0.2621 0.0095 248.1897);
|
||||||
|
--sidebar-primary: oklch(0.5502 0.1193 263.8209);
|
||||||
|
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
||||||
|
--sidebar-accent: oklch(0.9417 0.0052 247.8790);
|
||||||
|
--sidebar-accent-foreground: oklch(0.2621 0.0095 248.1897);
|
||||||
|
--sidebar-border: oklch(0.9109 0.0070 247.9014);
|
||||||
|
--sidebar-ring: oklch(0.5502 0.1193 263.8209);
|
||||||
|
--destructive-foreground: oklch(1.0000 0 0);
|
||||||
|
--font-sans: 'Inter', sans-serif;
|
||||||
|
--font-serif: 'Lora', serif;
|
||||||
|
--font-mono: 'JetBrains Mono', monospace;
|
||||||
|
--shadow-color: #000000;
|
||||||
|
--shadow-opacity: 0.05;
|
||||||
|
--shadow-blur: 0.5rem;
|
||||||
|
--shadow-spread: 0rem;
|
||||||
|
--shadow-offset-x: 0rem;
|
||||||
|
--shadow-offset-y: 0.1rem;
|
||||||
|
--letter-spacing: 0em;
|
||||||
|
--spacing: 0.25rem;
|
||||||
|
--shadow-2xs: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.03);
|
||||||
|
--shadow-xs: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.03);
|
||||||
|
--shadow-sm: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.05), 0rem 1px 2px -1px hsl(0 0% 0% / 0.05);
|
||||||
|
--shadow: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.05), 0rem 1px 2px -1px hsl(0 0% 0% / 0.05);
|
||||||
|
--shadow-md: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.05), 0rem 2px 4px -1px hsl(0 0% 0% / 0.05);
|
||||||
|
--shadow-lg: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.05), 0rem 4px 6px -1px hsl(0 0% 0% / 0.05);
|
||||||
|
--shadow-xl: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.05), 0rem 8px 10px -1px hsl(0 0% 0% / 0.05);
|
||||||
|
--shadow-2xl: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.13);
|
||||||
|
--tracking-normal: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: oklch(0.2270 0.0120 270.8402);
|
||||||
|
--foreground: oklch(0.9067 0 0);
|
||||||
|
--card: oklch(0.2630 0.0127 258.3724);
|
||||||
|
--card-foreground: oklch(0.9067 0 0);
|
||||||
|
--popover: oklch(0.2630 0.0127 258.3724);
|
||||||
|
--popover-foreground: oklch(0.9067 0 0);
|
||||||
|
--primary: oklch(0.5774 0.1248 263.3770);
|
||||||
|
--primary-foreground: oklch(1.0000 0 0);
|
||||||
|
--secondary: oklch(0.7636 0.0866 239.8852);
|
||||||
|
--secondary-foreground: oklch(0.2621 0.0095 248.1897);
|
||||||
|
--muted: oklch(0.3006 0.0156 264.3078);
|
||||||
|
--muted-foreground: oklch(0.7137 0.0192 261.3246);
|
||||||
|
--accent: oklch(0.3006 0.0156 264.3078);
|
||||||
|
--accent-foreground: oklch(0.9067 0 0);
|
||||||
|
--destructive: oklch(0.5915 0.2020 21.2388);
|
||||||
|
--border: oklch(0.3451 0.0133 248.2124);
|
||||||
|
--input: oklch(0.2630 0.0127 258.3724);
|
||||||
|
--ring: oklch(0.5502 0.1193 263.8209);
|
||||||
|
--chart-1: oklch(0.5502 0.1193 263.8209);
|
||||||
|
--chart-2: oklch(0.7499 0.0898 239.3977);
|
||||||
|
--chart-3: oklch(0.4711 0.0998 264.0792);
|
||||||
|
--chart-4: oklch(0.6689 0.0699 240.3096);
|
||||||
|
--chart-5: oklch(0.5107 0.1098 263.6921);
|
||||||
|
--sidebar: oklch(0.2270 0.0120 270.8402);
|
||||||
|
--sidebar-foreground: oklch(0.9067 0 0);
|
||||||
|
--sidebar-primary: oklch(0.5502 0.1193 263.8209);
|
||||||
|
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
||||||
|
--sidebar-accent: oklch(0.3006 0.0156 264.3078);
|
||||||
|
--sidebar-accent-foreground: oklch(0.9067 0 0);
|
||||||
|
--sidebar-border: oklch(0.3451 0.0133 248.2124);
|
||||||
|
--sidebar-ring: oklch(0.5502 0.1193 263.8209);
|
||||||
|
--destructive-foreground: oklch(1.0000 0 0);
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--font-sans: 'Inter', sans-serif;
|
||||||
|
--font-serif: 'Lora', serif;
|
||||||
|
--font-mono: 'JetBrains Mono', monospace;
|
||||||
|
--shadow-color: #000000;
|
||||||
|
--shadow-opacity: 0.3;
|
||||||
|
--shadow-blur: 0.5rem;
|
||||||
|
--shadow-spread: 0rem;
|
||||||
|
--shadow-offset-x: 0rem;
|
||||||
|
--shadow-offset-y: 0.1rem;
|
||||||
|
--letter-spacing: 0em;
|
||||||
|
--spacing: 0.25rem;
|
||||||
|
--shadow-2xs: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.15);
|
||||||
|
--shadow-xs: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.15);
|
||||||
|
--shadow-sm: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.30), 0rem 1px 2px -1px hsl(0 0% 0% / 0.30);
|
||||||
|
--shadow: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.30), 0rem 1px 2px -1px hsl(0 0% 0% / 0.30);
|
||||||
|
--shadow-md: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.30), 0rem 2px 4px -1px hsl(0 0% 0% / 0.30);
|
||||||
|
--shadow-lg: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.30), 0rem 4px 6px -1px hsl(0 0% 0% / 0.30);
|
||||||
|
--shadow-xl: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.30), 0rem 8px 10px -1px hsl(0 0% 0% / 0.30);
|
||||||
|
--shadow-2xl: 0rem 0.1rem 0.5rem 0rem hsl(0 0% 0% / 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
letter-spacing: var(--tracking-normal);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
client/src/lib/axios.ts
Normal file
24
client/src/lib/axios.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { AxiosError } from 'axios';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export const axiosClient = axios.create({
|
||||||
|
baseURL: '/api/',
|
||||||
|
});
|
||||||
|
|
||||||
|
axiosClient.interceptors.request.use((config) => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token !== null) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
axiosClient.interceptors.response.use(undefined, async (error: AxiosError) => {
|
||||||
|
if (error.response && error?.response.status === 401) {
|
||||||
|
// TODO: refresh token
|
||||||
|
if (error.config) {
|
||||||
|
return axiosClient(error.config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
7
client/src/lib/utils.ts
Normal file
7
client/src/lib/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { ClassValue } from 'clsx';
|
||||||
|
import { clsx } from 'clsx';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
27
client/src/main.tsx
Normal file
27
client/src/main.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { createRouter, RouterProvider } from '@tanstack/react-router';
|
||||||
|
import { StrictMode } from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
|
||||||
|
// Import the generated route tree
|
||||||
|
import { routeTree } from './routeTree.gen';
|
||||||
|
|
||||||
|
// Create a new router instance
|
||||||
|
const router = createRouter({ routeTree });
|
||||||
|
|
||||||
|
// Register the router instance for type safety
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface Register {
|
||||||
|
router: typeof router;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the app
|
||||||
|
const rootElement = document.getElementById('root')!;
|
||||||
|
if (!rootElement.innerHTML) {
|
||||||
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
|
root.render(
|
||||||
|
<StrictMode>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</StrictMode>,
|
||||||
|
);
|
||||||
|
}
|
||||||
59
client/src/routeTree.gen.ts
Normal file
59
client/src/routeTree.gen.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
|
||||||
|
// This file was automatically generated by TanStack Router.
|
||||||
|
// You should NOT make any changes in this file as it will be overwritten.
|
||||||
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
|
|
||||||
|
const IndexRoute = IndexRouteImport.update({
|
||||||
|
id: '/',
|
||||||
|
path: '/',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
export interface FileRoutesByFullPath {
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
}
|
||||||
|
export interface FileRoutesByTo {
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
}
|
||||||
|
export interface FileRoutesById {
|
||||||
|
__root__: typeof rootRouteImport
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
}
|
||||||
|
export interface FileRouteTypes {
|
||||||
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
|
fullPaths: '/'
|
||||||
|
fileRoutesByTo: FileRoutesByTo
|
||||||
|
to: '/'
|
||||||
|
id: '__root__' | '/'
|
||||||
|
fileRoutesById: FileRoutesById
|
||||||
|
}
|
||||||
|
export interface RootRouteChildren {
|
||||||
|
IndexRoute: typeof IndexRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface FileRoutesByPath {
|
||||||
|
'/': {
|
||||||
|
id: '/'
|
||||||
|
path: '/'
|
||||||
|
fullPath: '/'
|
||||||
|
preLoaderRoute: typeof IndexRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
|
IndexRoute: IndexRoute,
|
||||||
|
}
|
||||||
|
export const routeTree = rootRouteImport
|
||||||
|
._addFileChildren(rootRouteChildren)
|
||||||
|
._addFileTypes<FileRouteTypes>()
|
||||||
22
client/src/routes/__root.tsx
Normal file
22
client/src/routes/__root.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { createRootRoute, Outlet } from '@tanstack/react-router';
|
||||||
|
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
|
||||||
|
import { ThemeProvider } from '@/components/theme-provider';
|
||||||
|
import '@/index.css';
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
function RootLayout() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<Outlet />
|
||||||
|
</QueryClientProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
<TanStackRouterDevtools />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Route = createRootRoute({ component: RootLayout });
|
||||||
18
client/src/routes/index.tsx
Normal file
18
client/src/routes/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
import { Time } from '@/components/Time';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/')({
|
||||||
|
component: Index,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Index() {
|
||||||
|
return (
|
||||||
|
<div className="p-2">
|
||||||
|
<h3>Welcome Home!</h3>
|
||||||
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<Time />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
32
client/tsconfig.app.json
Normal file
32
client/tsconfig.app.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"module": "ESNext",
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
13
client/tsconfig.json
Normal file
13
client/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
],
|
||||||
|
"files": []
|
||||||
|
}
|
||||||
26
client/tsconfig.node.json
Normal file
26
client/tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"module": "ESNext",
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"types": ["node"],
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
22
client/vite.config.ts
Normal file
22
client/vite.config.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
|
import { tanstackRouter } from '@tanstack/router-plugin/vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
tanstackRouter({
|
||||||
|
target: 'react',
|
||||||
|
autoCodeSplitting: true,
|
||||||
|
}),
|
||||||
|
react(),
|
||||||
|
tailwindcss(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
11
config.default.yaml
Normal file
11
config.default.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
server:
|
||||||
|
address: :8000
|
||||||
|
debug_mode: false
|
||||||
|
file_logger: false
|
||||||
|
jwt_secret: someting
|
||||||
|
database:
|
||||||
|
type: postgres
|
||||||
|
host: 127.0.0.1
|
||||||
|
name: postgres
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
37
config/config.go
Normal file
37
config/config.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConfigDir() string {
|
||||||
|
env := os.Getenv("CONFIG_PATH")
|
||||||
|
if env != "" {
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
// Read global config
|
||||||
|
viper.SetConfigName("config")
|
||||||
|
viper.SetConfigType("yaml")
|
||||||
|
viper.AddConfigPath(ConfigDir())
|
||||||
|
|
||||||
|
// Bind ENV
|
||||||
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
|
conf := &config{}
|
||||||
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
|
// Dont generate config when using dev mode
|
||||||
|
log.Fatalln("Can't read config!")
|
||||||
|
}
|
||||||
|
if err := viper.Unmarshal(conf); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
14
config/env.go
Normal file
14
config/env.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TZ() string {
|
||||||
|
tz := os.Getenv("TZ")
|
||||||
|
|
||||||
|
if tz == "" {
|
||||||
|
return "Asia/Shanghai"
|
||||||
|
}
|
||||||
|
return tz
|
||||||
|
}
|
||||||
21
config/types.go
Normal file
21
config/types.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
Server server `yaml:"server"`
|
||||||
|
Database database `yaml:"database"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type server struct {
|
||||||
|
Address string `yaml:"address"`
|
||||||
|
DebugMode string `yaml:"debug_mode"`
|
||||||
|
FileLogger string `yaml:"file_logger"`
|
||||||
|
JwtSecret string `yaml:"jwt_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type database struct {
|
||||||
|
Type string `yaml:"type"`
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
}
|
||||||
39
data/data.go
Normal file
39
data/data.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/data/drivers"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Database *drivers.DBClient
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
// Init database
|
||||||
|
dbType := viper.GetString("database.type")
|
||||||
|
exDSN := drivers.ExternalDSN{
|
||||||
|
Host: viper.GetString("database.host"),
|
||||||
|
Name: viper.GetString("database.name"),
|
||||||
|
Username: viper.GetString("database.username"),
|
||||||
|
Password: viper.GetString("database.password"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if dbType != "postgres" {
|
||||||
|
log.Fatal("[Database] Only support postgras db!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conect to db
|
||||||
|
db, err := drivers.Postgres(exDSN)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("[Database] Error connecting to db!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto migrate
|
||||||
|
err = db.DB.AutoMigrate(&User{})
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[Database] Error migrating database: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Database = db
|
||||||
|
}
|
||||||
24
data/drivers/postgres.go
Normal file
24
data/drivers/postgres.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package drivers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/config"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SplitHostPort(url string) (host, port string) {
|
||||||
|
if !strings.Contains(url, ":") {
|
||||||
|
return url, "5432"
|
||||||
|
}
|
||||||
|
split := strings.Split(url, ":")
|
||||||
|
return split[0], split[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func Postgres(dsn ExternalDSN) (*DBClient, error) {
|
||||||
|
host, port := SplitHostPort(dsn.Host)
|
||||||
|
conn := "host=" + host + " user=" + dsn.Username + " password=" + dsn.Password + " dbname=" + dsn.Name + " port=" + port + " sslmode=disable TimeZone=" + config.TZ()
|
||||||
|
db, err := gorm.Open(postgres.Open(conn), &gorm.Config{})
|
||||||
|
return &DBClient{db}, err
|
||||||
|
}
|
||||||
16
data/drivers/types.go
Normal file
16
data/drivers/types.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package drivers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExternalDSN struct {
|
||||||
|
Host string
|
||||||
|
Name string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBClient struct {
|
||||||
|
*gorm.DB
|
||||||
|
}
|
||||||
38
data/user.go
Normal file
38
data/user.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Id uint `json:"id" gorm:"primarykey;autoincrement"`
|
||||||
|
UUID uuid.UUID `json:"uuid" gorm:"type:uuid;uniqueindex;not null"`
|
||||||
|
UserId uuid.UUID `json:"user_id" gorm:"size:8;uniqueindex;not null"`
|
||||||
|
Email string `json:"email" gorm:"uniqueindex;not null"`
|
||||||
|
Nickname string `json:"nickname" gorm:"not null"`
|
||||||
|
Type string `json:"type" gorm:"not null"`
|
||||||
|
Subtitle string `json:"subtitle" gorm:"not null"`
|
||||||
|
Avatar string `json:"avatar" gorm:"not null"`
|
||||||
|
Checkin bool `json:"checkin" gorm:"not null"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) GetByEmail(email string) error {
|
||||||
|
if err := Database.Where("email = ?", email).First(&self).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) GetByUserId(userId string) error {
|
||||||
|
if err := Database.Where("user_id = ?", userId).First(&self).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) SetCheckinState(email string, state bool) error {
|
||||||
|
if err := Database.Where("email = ?", email).First(&self).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
self.Checkin = state
|
||||||
|
Database.Save(&self)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
214
devenv.lock
Normal file
214
devenv.lock
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"devenv": {
|
||||||
|
"locked": {
|
||||||
|
"dir": "src/modules",
|
||||||
|
"lastModified": 1766087669,
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv",
|
||||||
|
"rev": "c03eed645ea94da7afbee29da76436b7ce33a5cb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"dir": "src/modules",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1765121682,
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat_2": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1765121682,
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"git-hooks": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"gitignore": "gitignore",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1765911976,
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"rev": "b68b780b69702a090c8bb1b973bab13756cc7a27",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"git-hooks_2": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat_2",
|
||||||
|
"gitignore": "gitignore_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"go-overlay",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1765911976,
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"rev": "b68b780b69702a090c8bb1b973bab13756cc7a27",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitignore": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"git-hooks",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1762808025,
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitignore_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"go-overlay",
|
||||||
|
"git-hooks",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1762808025,
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"go-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"git-hooks": "git-hooks_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1766126609,
|
||||||
|
"owner": "purpleclay",
|
||||||
|
"repo": "go-overlay",
|
||||||
|
"rev": "959f32b00fd3d462d4d570bd118b4be03c3f2019",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "purpleclay",
|
||||||
|
"repo": "go-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1764580874,
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv-nixpkgs",
|
||||||
|
"rev": "dcf61356c3ab25f1362b4a4428a6d871e84f1d1d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"ref": "rolling",
|
||||||
|
"repo": "devenv-nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"devenv": "devenv",
|
||||||
|
"git-hooks": "git-hooks",
|
||||||
|
"go-overlay": "go-overlay",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"pre-commit-hooks": [
|
||||||
|
"git-hooks"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
56
devenv.nix
Normal file
56
devenv.nix
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{ pkgs, config, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
env.GREET = "devenv";
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
pkgs.git
|
||||||
|
pkgs.bun
|
||||||
|
pkgs.just
|
||||||
|
];
|
||||||
|
|
||||||
|
dotenv = {
|
||||||
|
enable = true;
|
||||||
|
filename = [
|
||||||
|
".env.production"
|
||||||
|
".env.development"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
languages.go = {
|
||||||
|
enable = true;
|
||||||
|
version = "1.25.5";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.caddy = {
|
||||||
|
enable = true;
|
||||||
|
dataDir = "${config.env.DEVENV_STATE}/caddy";
|
||||||
|
config = ''
|
||||||
|
{
|
||||||
|
debug
|
||||||
|
}
|
||||||
|
:8080 {
|
||||||
|
root * ${config.env.DEVENV_ROOT}/.outputs/static
|
||||||
|
file_server
|
||||||
|
reverse_proxy /api/v1/* http://127.0.0.1:8000
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
services.redis = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.postgres = {
|
||||||
|
enable = true;
|
||||||
|
createDatabase = true;
|
||||||
|
listen_addresses = "127.0.0.1";
|
||||||
|
initialDatabases = [
|
||||||
|
{
|
||||||
|
name = "postgres";
|
||||||
|
user = "postgres";
|
||||||
|
pass = "postgres";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
8
devenv.yaml
Normal file
8
devenv.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
inputs:
|
||||||
|
go-overlay:
|
||||||
|
url: github:purpleclay/go-overlay
|
||||||
|
inputs:
|
||||||
|
nixpkgs:
|
||||||
|
follows: nixpkgs
|
||||||
|
nixpkgs:
|
||||||
|
url: github:cachix/devenv-nixpkgs/rolling
|
||||||
61
go.mod
Normal file
61
go.mod
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
module nixcn-cms
|
||||||
|
|
||||||
|
go 1.25.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
|
github.com/bytedance/sonic v1.14.2 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.11.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.29.0 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.19.1 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.57.1 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/spf13/viper v1.21.0 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
|
go.uber.org/mock v0.6.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/arch v0.23.0 // indirect
|
||||||
|
golang.org/x/crypto v0.46.0 // indirect
|
||||||
|
golang.org/x/mod v0.31.0 // indirect
|
||||||
|
golang.org/x/net v0.48.0 // indirect
|
||||||
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
|
golang.org/x/text v0.32.0 // indirect
|
||||||
|
golang.org/x/tools v0.40.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
|
gorm.io/driver/postgres v1.6.0 // indirect
|
||||||
|
gorm.io/gorm v1.31.1 // indirect
|
||||||
|
)
|
||||||
130
go.sum
Normal file
130
go.sum
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
|
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
|
||||||
|
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
|
||||||
|
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
|
||||||
|
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.29.0 h1:lQlF5VNJWNlRbRZNeOIkWElR+1LL/OuHcc0Kp14w1xk=
|
||||||
|
github.com/go-playground/validator/v10 v10.29.0/go.mod h1:D6QxqeMlgIPuT02L66f2ccrZ7AGgHkzKmmTMZhk/Kc4=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE=
|
||||||
|
github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
|
github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
|
||||||
|
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||||
|
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
|
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
|
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||||
|
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||||
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
|
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||||
|
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||||
|
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||||
|
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||||
|
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
77
internal/crypto/jwt/jwt.go
Normal file
77
internal/crypto/jwt/jwt.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Claims struct {
|
||||||
|
UserID uuid.UUID `json:"user_id"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func JWTAuth() gin.HandlerFunc {
|
||||||
|
var JwtSecret = []byte(viper.GetString("server.jwt_secret"))
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
auth := c.GetHeader("Authorization")
|
||||||
|
if auth == "" {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "missing Authorization header",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(auth, " ", 2)
|
||||||
|
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "invalid Authorization header format",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenStr := parts[1]
|
||||||
|
|
||||||
|
token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return JwtSecret, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil || !token.Valid {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "invalid or expired token",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := token.Claims.(*Claims)
|
||||||
|
if !ok {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "invalid token claims",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("user_id", claims.UserID)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateToken(userID uuid.UUID, application string) (string, error) {
|
||||||
|
var JwtSecret = []byte(viper.GetString("server.jwt_secret"))
|
||||||
|
claims := Claims{
|
||||||
|
UserID: userID,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: application,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
return token.SignedString(JwtSecret)
|
||||||
|
}
|
||||||
94
internal/crypto/jwt/jwt_test.go
Normal file
94
internal/crypto/jwt/jwt_test.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"nixcn-cms/config"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
config.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateTestToken(userID uuid.UUID, expire time.Duration) string {
|
||||||
|
var JwtSecret = []byte(viper.GetString("server.jwt_secret"))
|
||||||
|
claims := Claims{
|
||||||
|
UserID: userID,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expire)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
tokenStr, _ := token.SignedString(JwtSecret)
|
||||||
|
return tokenStr
|
||||||
|
}
|
||||||
|
func TestJWTAuth_MissingToken(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(JWTAuth())
|
||||||
|
r.GET("/test", func(c *gin.Context) {
|
||||||
|
c.JSON(200, gin.H{"ok": true})
|
||||||
|
})
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("expected 401, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestJWTAuth_InvalidToken(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(JWTAuth())
|
||||||
|
r.GET("/test", func(c *gin.Context) {
|
||||||
|
c.JSON(200, gin.H{"ok": true})
|
||||||
|
})
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer invalid.token.here")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("expected 401, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestJWTAuth_ValidToken(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(JWTAuth())
|
||||||
|
r.GET("/test", func(c *gin.Context) {
|
||||||
|
userID := c.GetUint("user_id")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"user_id": userID,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
uuid, _ := uuid.NewUUID()
|
||||||
|
token := generateTestToken(uuid, time.Hour)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
23
justfile
Normal file
23
justfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
project_name := "nixcn-cms"
|
||||||
|
project_dir := justfile_directory()
|
||||||
|
server_enrty := "main.go"
|
||||||
|
output_dir := join(project_dir, ".outputs")
|
||||||
|
client_dir := join(project_dir, "client")
|
||||||
|
exec_path := join(output_dir, project_name)
|
||||||
|
go_cmd := `realpath $(which go)`
|
||||||
|
bun_cmd := `realpath $(which bun)`
|
||||||
|
|
||||||
|
default: clean build run
|
||||||
|
|
||||||
|
clean:
|
||||||
|
find .outputs -mindepth 1 ! -name config.yaml -exec rm -rf {} +
|
||||||
|
|
||||||
|
build:
|
||||||
|
mkdir -p {{ output_dir }}
|
||||||
|
{{ go_cmd }} build -o {{ exec_path }}{{ if os() == "windows" { ".exe" } else { "" } }} {{ server_enrty }}
|
||||||
|
|
||||||
|
run:
|
||||||
|
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} {{ exec_path }}{{ if os() == "windows" { ".exe" } else { "" } }}
|
||||||
|
|
||||||
|
test:
|
||||||
|
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} GO_ENV=test go test -C .. ./...
|
||||||
60
logger/logrus.go
Normal file
60
logger/logrus.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
FileLogger := viper.GetBool("server.file_logger")
|
||||||
|
DebugMode := viper.GetBool("server.debug_mode")
|
||||||
|
|
||||||
|
if FileLogger == true {
|
||||||
|
gin.DisableConsoleColor()
|
||||||
|
file, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln("Error opening log file: ", err)
|
||||||
|
}
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
log.SetOutput(file)
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
} else {
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if DebugMode == true {
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
} else {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gin() gin.HandlerFunc {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
startTime := time.Now()
|
||||||
|
ctx.Next()
|
||||||
|
endTime := time.Now()
|
||||||
|
latencyTime := endTime.Sub(startTime)
|
||||||
|
reqMethod := ctx.Request.Method
|
||||||
|
reqUri := ctx.Request.RequestURI
|
||||||
|
statusCode := ctx.Writer.Status()
|
||||||
|
clientIP := ctx.ClientIP()
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"METHOD": reqMethod,
|
||||||
|
"URI": reqUri,
|
||||||
|
"STATUS": statusCode,
|
||||||
|
"LATENCY": latencyTime,
|
||||||
|
"CLIENT_IP": clientIP,
|
||||||
|
}).Info("HTTP REQUEST")
|
||||||
|
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
15
main.go
Normal file
15
main.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/config"
|
||||||
|
"nixcn-cms/data"
|
||||||
|
"nixcn-cms/logger"
|
||||||
|
"nixcn-cms/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config.Init()
|
||||||
|
logger.Init()
|
||||||
|
data.Init()
|
||||||
|
server.Start()
|
||||||
|
}
|
||||||
13
server/router.go
Normal file
13
server/router.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/service/check"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Router(e *gin.Engine) {
|
||||||
|
// API Services
|
||||||
|
api := e.Group("/api/v1")
|
||||||
|
check.Handler(api.Group("/check"))
|
||||||
|
}
|
||||||
32
server/server.go
Normal file
32
server/server.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"nixcn-cms/logger"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Start() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.Use(logger.Gin(), gin.Recovery())
|
||||||
|
|
||||||
|
Router(r)
|
||||||
|
|
||||||
|
// Start http server
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: viper.GetString("server.address"),
|
||||||
|
Handler: r,
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
MaxHeaderBytes: 1 << 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Starting server on " + viper.GetString("server.address"))
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
log.Errorf("Error starting server: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
5
service/check/checkin.go
Normal file
5
service/check/checkin.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package check
|
||||||
|
|
||||||
|
func Checkin() {
|
||||||
|
|
||||||
|
}
|
||||||
14
service/check/handler.go
Normal file
14
service/check/handler.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/internal/crypto/jwt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Handler(r *gin.RouterGroup) {
|
||||||
|
r.Use(jwt.JWTAuth())
|
||||||
|
r.GET("/test", func(ctx *gin.Context) {
|
||||||
|
ctx.JSON(200, gin.H{"Test": "Test"})
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user