all: publish code
This commit is contained in:
commit
f3db64fa38
100 changed files with 73612 additions and 0 deletions
1
.env
Normal file
1
.env
Normal file
|
|
@ -0,0 +1 @@
|
|||
SECRET=hello_world
|
||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Personal
|
||||
docs/
|
||||
app.db
|
||||
docker/server
|
||||
server
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
22
.vscode/launch.json
vendored
Normal file
22
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch server on external",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/cmd/server",
|
||||
"host": "127.0.0.1",
|
||||
"port": 10500,
|
||||
"showGlobalVariables": true,
|
||||
},
|
||||
{
|
||||
"name": "Launch server",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/cmd/server",
|
||||
"showGlobalVariables": true,
|
||||
}
|
||||
]
|
||||
}
|
||||
11
.vscode/tasks.json
vendored
Normal file
11
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Run dlv",
|
||||
"type": "shell",
|
||||
"command": "dlv dap --check-go-version --listen 127.0.0.1:10500",
|
||||
"group": "build",
|
||||
},
|
||||
],
|
||||
}
|
||||
55
Makefile
Normal file
55
Makefile
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
include Makefile.common
|
||||
include Makefile.sthrift
|
||||
include Makefile.autogen
|
||||
|
||||
# use zig cc/c++ to statically link deps
|
||||
TARGET_TRIPLE := x86_64-linux
|
||||
|
||||
CFLAGS ?=
|
||||
CFLAGS += -target $(TARGET_TRIPLE)
|
||||
|
||||
CXXFLAGS ?=
|
||||
CXXFLAGS += -target $(TARGET_TRIPLE)
|
||||
|
||||
GOFLAGS ?=
|
||||
GOFLAGS += -x -o docker/server
|
||||
|
||||
all:
|
||||
gen: gen-backend-thrift gen-frontend-thrift
|
||||
|
||||
clean: clean-public
|
||||
|
||||
clean-public:
|
||||
rm -r public/
|
||||
mkdir public
|
||||
echo "$$_G_PUBLIC_EXPORTER" > public/public.go
|
||||
|
||||
build: frontend-build backend-build
|
||||
build-dev: frontend-debug backend-build
|
||||
build.docker: build
|
||||
"docker" build -t f-ass-wpw:dev docker/
|
||||
|
||||
backend-dev:
|
||||
go run github.com/ii64/go-dlv-manager@latest
|
||||
backend-build: clean-public
|
||||
CC="zig cc $(CFLAGS)" CXX="zig c++ $(CXXFLAGS)" go build $(GOFLAGS) wpw-common/cmd/server
|
||||
|
||||
frontend-dev:
|
||||
$(MAKE) -C frontend dev
|
||||
frontend-debug: clean-public
|
||||
$(MAKE) -C frontend build ENV=dev
|
||||
frontend-build: clean-public
|
||||
$(MAKE) -C frontend build ENV=production
|
||||
frontend-preview:
|
||||
$(MAKE) -C frontend preview
|
||||
|
||||
gen-backend-thrift:
|
||||
$(MAKE) gen-idl \
|
||||
THRIFT_DIR_SRC=$(THRIFT_IDL_DIR) \
|
||||
THRIFT_DIR_OUT=$(THRIFT_GEN_DIR)
|
||||
gen-frontend-thrift:
|
||||
$(MAKE) gen-idl \
|
||||
THRIFT=thrift \
|
||||
THRIFTGO_GEN=js:"node,ts,es6" \
|
||||
THRIFT_DIR_SRC=$(THRIFT_IDL_DIR) \
|
||||
THRIFT_DIR_OUT=$(FRONTEND_THRIFT_GEN_DIR)
|
||||
9
Makefile.autogen
Normal file
9
Makefile.autogen
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
define _G_PUBLIC_EXPORTER
|
||||
package public
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *
|
||||
var FS embed.FS
|
||||
endef
|
||||
export _G_PUBLIC_EXPORTER
|
||||
7
Makefile.common
Normal file
7
Makefile.common
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
PKG := wpw-common
|
||||
|
||||
THRIFT_IDL_DIR := idl
|
||||
THRIFT_LIB := github.com/apache/thrift/lib/go/thrift
|
||||
THRIFT_GEN_PACKAGE_PREFIX := $(PKG)/pkg/gen
|
||||
THRIFT_GEN_DIR := pkg/gen
|
||||
FRONTEND_THRIFT_GEN_DIR := frontend/gen
|
||||
34
Makefile.sthrift
Normal file
34
Makefile.sthrift
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
include Makefile.common
|
||||
|
||||
THRIFT_GEN_REMOTE_PATTERN := *remote
|
||||
|
||||
THRIFTGO_GEN_FLAG := thrift_import_path=$(THRIFT_LIB),package_prefix=$(THRIFT_GEN_PACKAGE_PREFIX)
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),reorder_fields=true
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),frugal_tag=true
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),keep_unknown_fields=true
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),reserve_comments=true
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),nil_safe=false
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),compatible_names=true
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),gen_type_meta=true
|
||||
|
||||
# THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),value_type_in_container=true
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),validate_set=true
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),use_type_alias=true
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),gen_db_tag=true
|
||||
THRIFTGO_GEN_FLAG := $(THRIFTGO_GEN_FLAG),gen_setter=true
|
||||
|
||||
THRIFTGO_GEN := go:"$(THRIFTGO_GEN_FLAG)"
|
||||
THRIFT := thriftgo
|
||||
|
||||
# gen-idl need `THRIFT_DIR_SRC` as input, and `THRIFT_DIR_OUT` as output.
|
||||
.PHONY: gen-idl
|
||||
gen-idl:
|
||||
go run github.com/ii64/thrift-idl-builder \
|
||||
-errors \
|
||||
-wrk 10 \
|
||||
-source-dir $(THRIFT_DIR_SRC) \
|
||||
-o $(THRIFT_DIR_OUT) \
|
||||
-bin $(THRIFT) \
|
||||
-gen $(THRIFTGO_GEN) && \
|
||||
bash -c 'find $(THRIFTGO_DIR_OUT) -name "$(THRIFT_GEN_REMOTE_PATTERN)" -prune -exec bash -c "echo {} && rm -r {}" \;' && \
|
||||
echo OK
|
||||
20
README.md
Normal file
20
README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
## Project-Title
|
||||
|
||||
This is a Go+Vite(Vue) template that just quickly written to fulfil the need of personal stuff.
|
||||
Consider to read existing implementations before using.
|
||||
|
||||
Tech Stack used: Go, fasthttp, Apache Thrift/ThriftGo, GORM, Vite + Vue3 + TS
|
||||
|
||||
Development:
|
||||
|
||||
Ctrl+Shift+B - run instance
|
||||
F5 - attach debug
|
||||
|
||||
make frontend-dev
|
||||
|
||||
Run:
|
||||
|
||||
make build.docker
|
||||
docker run --rm -e SECRET=abc f-ass-wpw:dev
|
||||
|
||||
MIT. ntsc
|
||||
7
docker/Dockerfile
Normal file
7
docker/Dockerfile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
FROM alpine:latest
|
||||
|
||||
WORKDIR /app
|
||||
# yes, cheat. Ideally I would use multi stage build here
|
||||
# but in this case I'll just throw this away.
|
||||
COPY server /app/server
|
||||
ENTRYPOINT [ "/app/server" ]
|
||||
7
frontend/.babelrc
Normal file
7
frontend/.babelrc
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"plugins": [
|
||||
"minify-dead-code-elimination",
|
||||
"@babel/plugin-transform-classes",
|
||||
"@babel/plugin-syntax-class-properties",
|
||||
]
|
||||
}
|
||||
1
frontend/.env
Normal file
1
frontend/.env
Normal file
|
|
@ -0,0 +1 @@
|
|||
VITE_HUB_URL=http://127.0.0.1:8000/api/core/v1
|
||||
2
frontend/.env.production
Normal file
2
frontend/.env.production
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# VITE_HUB_URL=https://int-cip.i64.tech/api/core/v1
|
||||
VITE_HUB_URL=http://127.0.0.1:8000/api/core/v1
|
||||
18
frontend/.eslintrc.cjs
Normal file
18
frontend/.eslintrc.cjs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
rules: {
|
||||
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow-with-description' }],
|
||||
// '@typescript-eslint/ban-ts-ignore': 'off',
|
||||
// '@typescript-eslint/ban-ts-comment': 'off',
|
||||
},
|
||||
extends: [
|
||||
// 'plugin:vue/vue3-essential',
|
||||
'plugin:prettier/recommended',
|
||||
'eslint:recommended',
|
||||
'@vue/eslint-config-typescript/recommended',
|
||||
'@vue/eslint-config-prettier',
|
||||
],
|
||||
};
|
||||
26
frontend/.gitignore
vendored
Normal file
26
frontend/.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
|
||||
node_modules_linux
|
||||
node_modules_windows
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
8
frontend/.prettierrc.cjs
Normal file
8
frontend/.prettierrc.cjs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: "all",
|
||||
singleQuote: true,
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
endOfLine:"auto"
|
||||
};
|
||||
3
frontend/.vscode/extensions.json
vendored
Normal file
3
frontend/.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
17
frontend/Makefile
Normal file
17
frontend/Makefile
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
ifdef ENV
|
||||
ENV_FLAG := --mode $(ENV)
|
||||
endif
|
||||
|
||||
all: dev
|
||||
|
||||
typecheck:
|
||||
pnpm run typecheck
|
||||
lint:
|
||||
pnpm run lint
|
||||
|
||||
dev:
|
||||
pnpm run dev
|
||||
build:
|
||||
pnpm run build $(ENV_FLAG)
|
||||
preview:
|
||||
pnpm run preview
|
||||
16
frontend/README.md
Normal file
16
frontend/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
|
||||
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
|
||||
|
||||
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
|
||||
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
|
||||
|
||||
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
|
||||
9
frontend/env.d.ts
vendored
Normal file
9
frontend/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_HUB_URL: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
75
frontend/gen/CoreService.d.ts
vendored
Normal file
75
frontend/gen/CoreService.d.ts
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
//
|
||||
// Autogenerated by Thrift Compiler (0.16.0)
|
||||
//
|
||||
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
|
||||
import thrift = require('thrift');
|
||||
import Thrift = thrift.Thrift;
|
||||
import Q = thrift.Q;
|
||||
import Int64 = require('node-int64');
|
||||
import structs_ttypes = require('./structs_types');
|
||||
import exceptions_ttypes = require('./exceptions_types');
|
||||
|
||||
|
||||
import ttypes = require('./service_types');
|
||||
|
||||
declare class Client {
|
||||
private output: thrift.TTransport;
|
||||
private pClass: thrift.TProtocol;
|
||||
private _seqid: number;
|
||||
|
||||
constructor(output: thrift.TTransport, pClass: { new(trans: thrift.TTransport): thrift.TProtocol });
|
||||
|
||||
login(request: structs_ttypes.LoginRequest): Promise<structs_ttypes.LoginResponse>;
|
||||
|
||||
login(request: structs_ttypes.LoginRequest, callback?: (error: exceptions_ttypes.CoreServicesException, response: structs_ttypes.LoginResponse)=>void): void;
|
||||
|
||||
getProfile(request: structs_ttypes.GetProfileRequest): Promise<structs_ttypes.GetProfileResponse>;
|
||||
|
||||
getProfile(request: structs_ttypes.GetProfileRequest, callback?: (error: exceptions_ttypes.CoreServicesException, response: structs_ttypes.GetProfileResponse)=>void): void;
|
||||
|
||||
logout(request: structs_ttypes.LogoutRequest): Promise<structs_ttypes.LogoutResponse>;
|
||||
|
||||
logout(request: structs_ttypes.LogoutRequest, callback?: (error: exceptions_ttypes.CoreServicesException, response: structs_ttypes.LogoutResponse)=>void): void;
|
||||
|
||||
getUserList(request: structs_ttypes.GetUserListRequest): Promise<structs_ttypes.GetUserListResponse>;
|
||||
|
||||
getUserList(request: structs_ttypes.GetUserListRequest, callback?: (error: exceptions_ttypes.CoreServicesException, response: structs_ttypes.GetUserListResponse)=>void): void;
|
||||
|
||||
createUser(request: structs_ttypes.CreateUserRequest): Promise<structs_ttypes.CreateUserResponse>;
|
||||
|
||||
createUser(request: structs_ttypes.CreateUserRequest, callback?: (error: exceptions_ttypes.CoreServicesException, response: structs_ttypes.CreateUserResponse)=>void): void;
|
||||
|
||||
deleteUsers(request: structs_ttypes.DeleteUsersRequest): Promise<structs_ttypes.DeleteUsersResponse>;
|
||||
|
||||
deleteUsers(request: structs_ttypes.DeleteUsersRequest, callback?: (error: exceptions_ttypes.CoreServicesException, response: structs_ttypes.DeleteUsersResponse)=>void): void;
|
||||
|
||||
getWajibPajakList(request: structs_ttypes.GetWajibPajakListRequest): Promise<structs_ttypes.GetWajibPajakListResponse>;
|
||||
|
||||
getWajibPajakList(request: structs_ttypes.GetWajibPajakListRequest, callback?: (error: exceptions_ttypes.CoreServicesException, response: structs_ttypes.GetWajibPajakListResponse)=>void): void;
|
||||
|
||||
createWajibPajak(request: structs_ttypes.CreateWajibPajakRequest): Promise<structs_ttypes.CreateWajibPajakResponse>;
|
||||
|
||||
createWajibPajak(request: structs_ttypes.CreateWajibPajakRequest, callback?: (error: exceptions_ttypes.CoreServicesException, response: structs_ttypes.CreateWajibPajakResponse)=>void): void;
|
||||
|
||||
deleteWajibPajakList(request: structs_ttypes.DeleteWajibpajakListRequest): Promise<structs_ttypes.DeleteWajibpajakListResponse>;
|
||||
|
||||
deleteWajibPajakList(request: structs_ttypes.DeleteWajibpajakListRequest, callback?: (error: exceptions_ttypes.CoreServicesException, response: structs_ttypes.DeleteWajibpajakListResponse)=>void): void;
|
||||
}
|
||||
|
||||
declare class Processor {
|
||||
private _handler: object;
|
||||
|
||||
constructor(handler: object);
|
||||
process(input: thrift.TProtocol, output: thrift.TProtocol): void;
|
||||
process_login(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;
|
||||
process_getProfile(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;
|
||||
process_logout(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;
|
||||
process_getUserList(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;
|
||||
process_createUser(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;
|
||||
process_deleteUsers(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;
|
||||
process_getWajibPajakList(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;
|
||||
process_createWajibPajak(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;
|
||||
process_deleteWajibPajakList(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;
|
||||
}
|
||||
2027
frontend/gen/CoreService.js
Normal file
2027
frontend/gen/CoreService.js
Normal file
File diff suppressed because it is too large
Load diff
18
frontend/gen/exceptions_types.d.ts
vendored
Normal file
18
frontend/gen/exceptions_types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// Autogenerated by Thrift Compiler (0.16.0)
|
||||
//
|
||||
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
import thrift = require('thrift');
|
||||
import Thrift = thrift.Thrift;
|
||||
import Q = thrift.Q;
|
||||
import Int64 = require('node-int64');
|
||||
|
||||
|
||||
declare class CoreServicesException extends Thrift.TException {
|
||||
public code: number;
|
||||
public message: string;
|
||||
public parameters: { [k: string]: string; };
|
||||
|
||||
constructor(args?: { code: number; message: string; parameters: { [k: string]: string; }; });
|
||||
}
|
||||
114
frontend/gen/exceptions_types.js
Normal file
114
frontend/gen/exceptions_types.js
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
//
|
||||
// Autogenerated by Thrift Compiler (0.16.0)
|
||||
//
|
||||
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
"use strict";
|
||||
|
||||
const thrift = require('thrift');
|
||||
const Thrift = thrift.Thrift;
|
||||
const Int64 = require('node-int64');
|
||||
|
||||
|
||||
const ttypes = module.exports = {};
|
||||
const CoreServicesException = module.exports.CoreServicesException = class extends Thrift.TException {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
this.name = "CoreServicesException";
|
||||
this.code = null;
|
||||
this.message = null;
|
||||
this.parameters = null;
|
||||
if (args) {
|
||||
if (args.code !== undefined && args.code !== null) {
|
||||
this.code = args.code;
|
||||
}
|
||||
if (args.message !== undefined && args.message !== null) {
|
||||
this.message = args.message;
|
||||
}
|
||||
if (args.parameters !== undefined && args.parameters !== null) {
|
||||
this.parameters = Thrift.copyMap(args.parameters, [null]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
read (input) {
|
||||
input.readStructBegin();
|
||||
while (true) {
|
||||
const ret = input.readFieldBegin();
|
||||
const ftype = ret.ftype;
|
||||
const fid = ret.fid;
|
||||
if (ftype == Thrift.Type.STOP) {
|
||||
break;
|
||||
}
|
||||
switch (fid) {
|
||||
case 1:
|
||||
if (ftype == Thrift.Type.I32) {
|
||||
this.code = input.readI32();
|
||||
} else {
|
||||
input.skip(ftype);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (ftype == Thrift.Type.STRING) {
|
||||
this.message = input.readString();
|
||||
} else {
|
||||
input.skip(ftype);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (ftype == Thrift.Type.MAP) {
|
||||
this.parameters = {};
|
||||
const _rtmp31 = input.readMapBegin();
|
||||
const _size0 = _rtmp31.size || 0;
|
||||
for (let _i2 = 0; _i2 < _size0; ++_i2) {
|
||||
let key3 = null;
|
||||
let val4 = null;
|
||||
key3 = input.readString();
|
||||
val4 = input.readString();
|
||||
this.parameters[key3] = val4;
|
||||
}
|
||||
input.readMapEnd();
|
||||
} else {
|
||||
input.skip(ftype);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
input.skip(ftype);
|
||||
}
|
||||
input.readFieldEnd();
|
||||
}
|
||||
input.readStructEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
write (output) {
|
||||
output.writeStructBegin('CoreServicesException');
|
||||
if (this.code !== null && this.code !== undefined) {
|
||||
output.writeFieldBegin('code', Thrift.Type.I32, 1);
|
||||
output.writeI32(this.code);
|
||||
output.writeFieldEnd();
|
||||
}
|
||||
if (this.message !== null && this.message !== undefined) {
|
||||
output.writeFieldBegin('message', Thrift.Type.STRING, 2);
|
||||
output.writeString(this.message);
|
||||
output.writeFieldEnd();
|
||||
}
|
||||
if (this.parameters !== null && this.parameters !== undefined) {
|
||||
output.writeFieldBegin('parameters', Thrift.Type.MAP, 3);
|
||||
output.writeMapBegin(Thrift.Type.STRING, Thrift.Type.STRING, Thrift.objectLength(this.parameters));
|
||||
for (let kiter5 in this.parameters) {
|
||||
if (this.parameters.hasOwnProperty(kiter5)) {
|
||||
let viter6 = this.parameters[kiter5];
|
||||
output.writeString(kiter5);
|
||||
output.writeString(viter6);
|
||||
}
|
||||
}
|
||||
output.writeMapEnd();
|
||||
output.writeFieldEnd();
|
||||
}
|
||||
output.writeFieldStop();
|
||||
output.writeStructEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
18
frontend/gen/exceptionsc_types.d.ts
vendored
Normal file
18
frontend/gen/exceptionsc_types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// Autogenerated by Thrift Compiler (0.16.0)
|
||||
//
|
||||
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
import thrift = require('thrift');
|
||||
import Thrift = thrift.Thrift;
|
||||
import Q = thrift.Q;
|
||||
import Int64 = require('node-int64');
|
||||
|
||||
|
||||
declare class CommonException extends Thrift.TException {
|
||||
public code: number;
|
||||
public message: string;
|
||||
public metadata?: { [k: string]: string; };
|
||||
|
||||
constructor(args?: { code: number; message: string; metadata?: { [k: string]: string; }; });
|
||||
}
|
||||
114
frontend/gen/exceptionsc_types.js
Normal file
114
frontend/gen/exceptionsc_types.js
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
//
|
||||
// Autogenerated by Thrift Compiler (0.16.0)
|
||||
//
|
||||
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
"use strict";
|
||||
|
||||
const thrift = require('thrift');
|
||||
const Thrift = thrift.Thrift;
|
||||
const Int64 = require('node-int64');
|
||||
|
||||
|
||||
const ttypes = module.exports = {};
|
||||
const CommonException = module.exports.CommonException = class extends Thrift.TException {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
this.name = "CommonException";
|
||||
this.code = null;
|
||||
this.message = null;
|
||||
this.metadata = null;
|
||||
if (args) {
|
||||
if (args.code !== undefined && args.code !== null) {
|
||||
this.code = args.code;
|
||||
}
|
||||
if (args.message !== undefined && args.message !== null) {
|
||||
this.message = args.message;
|
||||
}
|
||||
if (args.metadata !== undefined && args.metadata !== null) {
|
||||
this.metadata = Thrift.copyMap(args.metadata, [null]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
read (input) {
|
||||
input.readStructBegin();
|
||||
while (true) {
|
||||
const ret = input.readFieldBegin();
|
||||
const ftype = ret.ftype;
|
||||
const fid = ret.fid;
|
||||
if (ftype == Thrift.Type.STOP) {
|
||||
break;
|
||||
}
|
||||
switch (fid) {
|
||||
case 1:
|
||||
if (ftype == Thrift.Type.I32) {
|
||||
this.code = input.readI32();
|
||||
} else {
|
||||
input.skip(ftype);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (ftype == Thrift.Type.STRING) {
|
||||
this.message = input.readString();
|
||||
} else {
|
||||
input.skip(ftype);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (ftype == Thrift.Type.MAP) {
|
||||
this.metadata = {};
|
||||
const _rtmp31 = input.readMapBegin();
|
||||
const _size0 = _rtmp31.size || 0;
|
||||
for (let _i2 = 0; _i2 < _size0; ++_i2) {
|
||||
let key3 = null;
|
||||
let val4 = null;
|
||||
key3 = input.readString();
|
||||
val4 = input.readString();
|
||||
this.metadata[key3] = val4;
|
||||
}
|
||||
input.readMapEnd();
|
||||
} else {
|
||||
input.skip(ftype);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
input.skip(ftype);
|
||||
}
|
||||
input.readFieldEnd();
|
||||
}
|
||||
input.readStructEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
write (output) {
|
||||
output.writeStructBegin('CommonException');
|
||||
if (this.code !== null && this.code !== undefined) {
|
||||
output.writeFieldBegin('code', Thrift.Type.I32, 1);
|
||||
output.writeI32(this.code);
|
||||
output.writeFieldEnd();
|
||||
}
|
||||
if (this.message !== null && this.message !== undefined) {
|
||||
output.writeFieldBegin('message', Thrift.Type.STRING, 2);
|
||||
output.writeString(this.message);
|
||||
output.writeFieldEnd();
|
||||
}
|
||||
if (this.metadata !== null && this.metadata !== undefined) {
|
||||
output.writeFieldBegin('metadata', Thrift.Type.MAP, 3);
|
||||
output.writeMapBegin(Thrift.Type.STRING, Thrift.Type.STRING, Thrift.objectLength(this.metadata));
|
||||
for (let kiter5 in this.metadata) {
|
||||
if (this.metadata.hasOwnProperty(kiter5)) {
|
||||
let viter6 = this.metadata[kiter5];
|
||||
output.writeString(kiter5);
|
||||
output.writeString(viter6);
|
||||
}
|
||||
}
|
||||
output.writeMapEnd();
|
||||
output.writeFieldEnd();
|
||||
}
|
||||
output.writeFieldStop();
|
||||
output.writeStructEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
13
frontend/gen/service_types.d.ts
vendored
Normal file
13
frontend/gen/service_types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// Autogenerated by Thrift Compiler (0.16.0)
|
||||
//
|
||||
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
import thrift = require('thrift');
|
||||
import Thrift = thrift.Thrift;
|
||||
import Q = thrift.Q;
|
||||
import Int64 = require('node-int64');
|
||||
import structs_ttypes = require('./structs_types');
|
||||
import exceptions_ttypes = require('./exceptions_types');
|
||||
|
||||
|
||||
16
frontend/gen/service_types.js
Normal file
16
frontend/gen/service_types.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// Autogenerated by Thrift Compiler (0.16.0)
|
||||
//
|
||||
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
"use strict";
|
||||
|
||||
const thrift = require('thrift');
|
||||
const Thrift = thrift.Thrift;
|
||||
const Int64 = require('node-int64');
|
||||
|
||||
const structs_ttypes = require('./structs_types');
|
||||
const exceptions_ttypes = require('./exceptions_types');
|
||||
|
||||
|
||||
const ttypes = module.exports = {};
|
||||
209
frontend/gen/structs_types.d.ts
vendored
Normal file
209
frontend/gen/structs_types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
//
|
||||
// Autogenerated by Thrift Compiler (0.16.0)
|
||||
//
|
||||
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
import thrift = require('thrift');
|
||||
import Thrift = thrift.Thrift;
|
||||
import Q = thrift.Q;
|
||||
import Int64 = require('node-int64');
|
||||
|
||||
|
||||
declare enum RoleType {
|
||||
UNKNOWN = 0,
|
||||
SYSTEM = 1,
|
||||
USER = 2,
|
||||
WP_OWNER = 100,
|
||||
WP_ADMIN = 110,
|
||||
}
|
||||
|
||||
declare enum JenisPajak {
|
||||
UNKNOWN = 0,
|
||||
PPH_21 = 1,
|
||||
PPH_23 = 2,
|
||||
PPH_25 = 3,
|
||||
PPH_26 = 4,
|
||||
PPH_4_2 = 5,
|
||||
PPH_15 = 6,
|
||||
PPN = 7,
|
||||
TAHUNAN = 8,
|
||||
}
|
||||
|
||||
declare enum WajibPajakOwnership {
|
||||
UNKNOWN = 0,
|
||||
OWNED = 1,
|
||||
ROLE = 2,
|
||||
}
|
||||
|
||||
declare class Role {
|
||||
public id: Int64;
|
||||
public displayName: string;
|
||||
public roleType: RoleType;
|
||||
public creator?: User;
|
||||
public users?: User[];
|
||||
public wajibPajakList?: WajibPajak[];
|
||||
|
||||
constructor(args?: { id: Int64; displayName: string; roleType: RoleType; creator?: User; users?: User[]; wajibPajakList?: WajibPajak[]; });
|
||||
}
|
||||
|
||||
declare class User {
|
||||
public id: Int64;
|
||||
public username: string;
|
||||
public password: string;
|
||||
public displayName: string;
|
||||
public privateKey: Buffer;
|
||||
public publicKey: Buffer;
|
||||
public roles?: Role[];
|
||||
public ownedWajibPajakList?: WajibPajak[];
|
||||
public rolesWajibPajakList?: Role[];
|
||||
|
||||
constructor(args?: { id: Int64; username: string; password: string; displayName: string; privateKey: Buffer; publicKey: Buffer; roles?: Role[]; ownedWajibPajakList?: WajibPajak[]; rolesWajibPajakList?: Role[]; });
|
||||
}
|
||||
|
||||
declare class WajibPajakProfile {
|
||||
public npwp: string;
|
||||
public displayName: string;
|
||||
public address: string;
|
||||
|
||||
constructor(args?: { npwp: string; displayName: string; address: string; });
|
||||
}
|
||||
|
||||
declare class WajibPajakTaxObligation {
|
||||
public id: Int64;
|
||||
public obligation: JenisPajak;
|
||||
public isActive: boolean;
|
||||
|
||||
constructor(args?: { id: Int64; obligation: JenisPajak; isActive: boolean; });
|
||||
}
|
||||
|
||||
declare class WajibPajak {
|
||||
public id: Int64;
|
||||
public profile: WajibPajakProfile;
|
||||
public owners: User[];
|
||||
public taxObligations?: WajibPajakTaxObligation[];
|
||||
public roles?: Role[];
|
||||
|
||||
constructor(args?: { id: Int64; profile: WajibPajakProfile; owners: User[]; taxObligations?: WajibPajakTaxObligation[]; roles?: Role[]; });
|
||||
}
|
||||
|
||||
declare class Pagination {
|
||||
public page: Int64;
|
||||
public rowsPerPage: Int64;
|
||||
|
||||
constructor(args?: { page: Int64; rowsPerPage: Int64; });
|
||||
}
|
||||
|
||||
declare class AlertInfo {
|
||||
public title: string;
|
||||
public description: string;
|
||||
|
||||
constructor(args?: { title: string; description: string; });
|
||||
}
|
||||
|
||||
declare class LoginRequest {
|
||||
public username: string;
|
||||
public password: string;
|
||||
|
||||
constructor(args?: { username: string; password: string; });
|
||||
}
|
||||
|
||||
declare class LoginResponse {
|
||||
public trkToken: string;
|
||||
public token: string;
|
||||
public profile?: User;
|
||||
|
||||
constructor(args?: { trkToken: string; token: string; profile?: User; });
|
||||
}
|
||||
|
||||
declare class LogoutRequest {
|
||||
public token: string;
|
||||
|
||||
constructor(args?: { token: string; });
|
||||
}
|
||||
|
||||
declare class LogoutResponse {
|
||||
}
|
||||
|
||||
declare class GetProfileRequest {
|
||||
}
|
||||
|
||||
declare class GetProfileResponse {
|
||||
public profile: User;
|
||||
|
||||
constructor(args?: { profile: User; });
|
||||
}
|
||||
|
||||
declare class GetUserListRequest {
|
||||
public pagination: Pagination;
|
||||
public searchTerm: string;
|
||||
|
||||
constructor(args?: { pagination: Pagination; searchTerm: string; });
|
||||
}
|
||||
|
||||
declare class GetUserListResponse {
|
||||
public pagination: Pagination;
|
||||
public totalUsers: Int64;
|
||||
public users: User[];
|
||||
|
||||
constructor(args?: { pagination: Pagination; totalUsers: Int64; users: User[]; });
|
||||
}
|
||||
|
||||
declare class CreateUserRequest {
|
||||
public user: User;
|
||||
|
||||
constructor(args?: { user: User; });
|
||||
}
|
||||
|
||||
declare class CreateUserResponse {
|
||||
}
|
||||
|
||||
declare class DeleteUsersRequest {
|
||||
public userIds: Int64[];
|
||||
|
||||
constructor(args?: { userIds: Int64[]; });
|
||||
}
|
||||
|
||||
declare class DeleteUsersResponse {
|
||||
public success: Int64[];
|
||||
public ignored: Int64[];
|
||||
|
||||
constructor(args?: { success: Int64[]; ignored: Int64[]; });
|
||||
}
|
||||
|
||||
declare class GetWajibPajakListRequest {
|
||||
public pagination: Pagination;
|
||||
public ownership: WajibPajakOwnership;
|
||||
public searchTerm: string;
|
||||
|
||||
constructor(args?: { pagination: Pagination; ownership: WajibPajakOwnership; searchTerm: string; });
|
||||
}
|
||||
|
||||
declare class GetWajibPajakListResponse {
|
||||
public pagination: Pagination;
|
||||
public totalWajibPajak: Int64;
|
||||
public wajibPajakList: WajibPajak[];
|
||||
|
||||
constructor(args?: { pagination: Pagination; totalWajibPajak: Int64; wajibPajakList: WajibPajak[]; });
|
||||
}
|
||||
|
||||
declare class CreateWajibPajakRequest {
|
||||
public wajibPajak: WajibPajak;
|
||||
|
||||
constructor(args?: { wajibPajak: WajibPajak; });
|
||||
}
|
||||
|
||||
declare class CreateWajibPajakResponse {
|
||||
}
|
||||
|
||||
declare class DeleteWajibpajakListRequest {
|
||||
public wpIds: Int64[];
|
||||
|
||||
constructor(args?: { wpIds: Int64[]; });
|
||||
}
|
||||
|
||||
declare class DeleteWajibpajakListResponse {
|
||||
public success: Int64[];
|
||||
public ignored: Int64[];
|
||||
|
||||
constructor(args?: { success: Int64[]; ignored: Int64[]; });
|
||||
}
|
||||
2028
frontend/gen/structs_types.js
Normal file
2028
frontend/gen/structs_types.js
Normal file
File diff suppressed because it is too large
Load diff
13
frontend/index.html
Normal file
13
frontend/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>WPW-CTL</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
95
frontend/package.json
Normal file
95
frontend/package.json
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"typecheck": "vuedx-typecheck .",
|
||||
"lint": "eslint --fix .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@algolia/client-search": "^4.14.2",
|
||||
"@heroicons/vue": "^2.0.13",
|
||||
"@vueuse/core": "^9.4.0",
|
||||
"classnames": "^2.3.2",
|
||||
"flowbite": "1.5.0",
|
||||
"flowbite-vue": "^0.0.6",
|
||||
"pinia": "^2.0.23",
|
||||
"thrift": "^0.16.0",
|
||||
"vue": "^3.2.41",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-easy-data-table": "^1.5.12",
|
||||
"vue3-perfect-scrollbar": "^1.6.1"
|
||||
|
||||
},
|
||||
"devDependencies": {
|
||||
"node-stdlib-browser": "^1.2.0",
|
||||
"@ampproject/rollup-plugin-closure-compiler": "^0.27.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/plugin-syntax-class-properties": "^7.12.13",
|
||||
"@babel/plugin-transform-classes": "^7.19.0",
|
||||
"@babel/preset-env": "^7.19.4",
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
|
||||
"@lopatnov/rollup-plugin-uglify": "^2.1.5",
|
||||
"@originjs/vite-plugin-commonjs": "^1.0.3",
|
||||
"@rollup/plugin-babel": "^6.0.2",
|
||||
"@rollup/plugin-commonjs": "^22.0.2",
|
||||
"@rollup/plugin-inject": "^4.0.4",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-terser": "^0.1.0",
|
||||
"@rushstack/eslint-patch": "^1.2.0",
|
||||
"@types/node": "^16.18.3",
|
||||
"@types/thrift": "^0.10.11",
|
||||
"@vitejs/plugin-legacy": "^2.3.0",
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"@vitejs/plugin-vue-jsx": "^2.1.0",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"@vuedx/typecheck": "^0.7.6",
|
||||
"@vuedx/typescript-plugin-vue": "^0.7.6",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"babel-plugin-minify-dead-code-elimination": "^0.5.2",
|
||||
"class-names": "^1.0.0",
|
||||
"esbuild": "*",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.7.0",
|
||||
"node-int64": "^0.4.0",
|
||||
"postcss": "^8.4.18",
|
||||
"prettier": "^2.7.1",
|
||||
"rollup": "^2.79.0",
|
||||
"rollup-obfuscator": "^3.0.1",
|
||||
"rollup-plugin-node-globals": "^1.4.0",
|
||||
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||
"tailwindcss": "^3.2.2",
|
||||
"terser": "^5.15.1",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.2.0",
|
||||
"vite-plugin-chunk-split": "^0.4.3",
|
||||
"vite-plugin-node-stdlib-browser": "^0.1.1",
|
||||
"vue-tsc": "^1.0.9"
|
||||
},
|
||||
"eslintConfigxxx": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"extendsxx": [
|
||||
"plugin:vue/essential",
|
||||
"plugin:prettier/recommended",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
}
|
||||
}
|
||||
6337
frontend/pnpm-lock.yaml
Normal file
6337
frontend/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
6
frontend/postcss.config.cjs
Normal file
6
frontend/postcss.config.cjs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
1
frontend/public/vite.svg
Normal file
1
frontend/public/vite.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
77
frontend/src/App.vue
Normal file
77
frontend/src/App.vue
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<script setup lang="ts">
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
|
||||
import NavigationBar from './components/NavigationBar.vue';
|
||||
import { useHub } from './hub';
|
||||
import { useTheme } from './stores/theme';
|
||||
import /* * as */structs from '@thriftgen/structs_types';
|
||||
import /* * as */exceptions from '@thriftgen/exceptions_types';
|
||||
import { RouteMeta, useRouter } from 'vue-router';
|
||||
import { AlertType, useAlert } from './stores/alert';
|
||||
import { useAuth } from './stores/auth';
|
||||
import { watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const authRef = storeToRefs(useAuth());
|
||||
const hub = useHub();
|
||||
const theme = useTheme();
|
||||
const themeRef = storeToRefs(theme);
|
||||
const router = useRouter();
|
||||
|
||||
async function fetchProfile() {
|
||||
const alert = useAlert();
|
||||
const auth = useAuth();
|
||||
|
||||
try {
|
||||
const resp = await hub.BizCore.client.getProfile(new structs.GetProfileRequest());
|
||||
console.log('get profile', resp);
|
||||
auth.setProfile(resp.profile);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('get profile', e);
|
||||
if (e instanceof exceptions.CoreServicesException) {
|
||||
alert.showContent('Attention', 'Login first', { type: AlertType.Error });
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
router.beforeResolve(async (to) => {
|
||||
// inherit navigation bar visibility from route meta
|
||||
theme.setNavigationBarVisibility(
|
||||
to.meta.displayNavigationBar == undefined ? true : to.meta.displayNavigationBar,
|
||||
);
|
||||
|
||||
// fetch user profile
|
||||
const validProfile = await fetchProfile();
|
||||
if (to.meta.requiresAuth) {
|
||||
if (!validProfile) return '/login';
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
// monitor state change of logged in
|
||||
watch(authRef.isLoggedIn, (loggedIn) => {
|
||||
const meta = router.currentRoute.value.meta;
|
||||
if (meta.requiresAuth && !loggedIn) {
|
||||
router.push('/login');
|
||||
}
|
||||
});
|
||||
|
||||
// monitor state change of darh theme
|
||||
watch(themeRef.isDarkThemeEnabled, (darkTheme) => {
|
||||
if (darkTheme) {
|
||||
document.documentElement.classList.add('dark-theme');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark-theme');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<NavigationBar v-if="theme.navigationBarVisibility" />
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* */
|
||||
</style>
|
||||
1
frontend/src/assets/vue.svg
Normal file
1
frontend/src/assets/vue.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
53
frontend/src/components/AlertBox.vue
Normal file
53
frontend/src/components/AlertBox.vue
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<script lang="ts" setup>
|
||||
import { useAlert, AlertType } from '@/stores/alert';
|
||||
|
||||
const data = useAlert();
|
||||
function hideAlert() {
|
||||
data.hide();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-if="data.show"
|
||||
:class="[
|
||||
(() => {
|
||||
switch (data.type) {
|
||||
case AlertType.Error:
|
||||
return 'text-red-700 bg-red-100 dark:bg-red-200 dark:text-red-800';
|
||||
case AlertType.Warning:
|
||||
return 'text-yellow-700 bg-yellow-100 dark:bg-yellow-200 dark:text-yellow-800';
|
||||
case AlertType.Success:
|
||||
return 'text-green-700 bg-green-100 dark:bg-green-200 dark:text-green-800';
|
||||
}
|
||||
return '';
|
||||
})(),
|
||||
'flex p-4 mb-6 text-sm rounded-lg',
|
||||
data.className,
|
||||
]"
|
||||
role="alert"
|
||||
>
|
||||
<span class="font-medium">{{ data.title }}: </span>
|
||||
{{ data.description }}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="hideAlert"
|
||||
class="ml-auto -mx-1.5 -my-1.5 rounded-lg focus:ring-2 p-1.5 inline-flex h-8 w-8"
|
||||
>
|
||||
<span class="sr-only">Close</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="w-5 h-5"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
144
frontend/src/components/NavigationBar.vue
Normal file
144
frontend/src/components/NavigationBar.vue
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<script lang="ts" setup>
|
||||
import { useHub } from '@/hub';
|
||||
import { useAlert } from '@/stores/alert';
|
||||
import { useAuth } from '@/stores/auth';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { RouteLocationRaw, RouterLink, useRouter } from 'vue-router';
|
||||
import /* * as */structs from '@thriftgen/structs_types';
|
||||
import SpinnerIcon from './SpinnerIcon.vue';
|
||||
import { NavbarLinkMeta } from '@/router';
|
||||
const auth = useAuth();
|
||||
const router = useRouter();
|
||||
const alert = useAlert();
|
||||
|
||||
const hub = useHub();
|
||||
|
||||
const isOnLoginPage = computed(() => router.currentRoute.value.path === '/login');
|
||||
const isLoading = ref(false);
|
||||
|
||||
async function authCtl() {
|
||||
isLoading.value = true;
|
||||
if (auth.isLoggedIn) {
|
||||
try {
|
||||
const req = new structs.LogoutRequest({ token: auth.authToken });
|
||||
const resp = await hub.BizCore.client.logout(req);
|
||||
console.log('logout', resp);
|
||||
auth.logout();
|
||||
alert.showContent('Attention', 'Logged out');
|
||||
// router.push('/login');
|
||||
} catch (e) {
|
||||
console.error('logout', e);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
isLoading.value = false;
|
||||
router.push('/login');
|
||||
}
|
||||
}
|
||||
|
||||
const navLink = computed(() => {
|
||||
const qs = router
|
||||
.getRoutes()
|
||||
.filter((x) => x.meta.navbarLink)
|
||||
.filter((x) => !x.meta.requiresAuth || (x.meta.requiresAuth && auth.isLoggedIn))
|
||||
.map((x) => ({ to: x.path, ...x.meta.navbarLink }))
|
||||
.sort((x, y) => (y.priority || 0) - (x.priority || 0));
|
||||
return qs;
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<nav class="bg-white border-gray-200 px-2 sm:px-4 py-2.5 rounded dark:bg-gray-900">
|
||||
<div class="container flex flex-wrap justify-between items-center mx-auto">
|
||||
<a href="/" class="flex items-center">
|
||||
<img
|
||||
src="https://flowbite.com/docs/images/logo.svg"
|
||||
class="mr-3 h-6 sm:h-9"
|
||||
alt="Flowbite Logo"
|
||||
/>
|
||||
<span class="self-center text-xl font-semibold whitespace-nowrap dark:text-white"
|
||||
>WPW-CTL</span
|
||||
>
|
||||
</a>
|
||||
<button
|
||||
data-collapse-toggle="navbar-default"
|
||||
type="button"
|
||||
class="inline-flex items-center p-2 ml-3 text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
|
||||
aria-controls="navbar-default"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
|
||||
<ul
|
||||
class="flex flex-col p-4 mt-4 bg-gray-50 rounded-lg border border-gray-100 md:flex-row md:space-x-8 md:mt-0 md:text-sm md:font-medium md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700"
|
||||
>
|
||||
<li v-if="auth.isLoggedIn && auth.profile">
|
||||
{{ auth.profile?.displayName }} ({{ auth.profile?.username }})
|
||||
</li>
|
||||
|
||||
<li v-for="(link, i) in navLink" :key="i">
|
||||
<RouterLink :to="link.to" :class="link.classList" aria-current="date">
|
||||
{{ link.caption }}
|
||||
</RouterLink>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<RouterLink
|
||||
to="#"
|
||||
v-if="!isOnLoginPage"
|
||||
@click="authCtl"
|
||||
class="block py-2 pr-4 pl-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 dark:text-white"
|
||||
aria-current="page"
|
||||
>
|
||||
<SpinnerIcon class="text-blue" v-if="isLoading" />
|
||||
{{ auth.isLoggedIn ? 'Logout' : 'Login' }}</RouterLink
|
||||
>
|
||||
</li>
|
||||
|
||||
<!-- <li>
|
||||
<a
|
||||
href="#"
|
||||
class="block py-2 pr-4 pl-3 text-gray-700 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
|
||||
>About</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="block py-2 pr-4 pl-3 text-gray-700 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
|
||||
>Services</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="block py-2 pr-4 pl-3 text-gray-700 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
|
||||
>Pricing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="block py-2 pr-4 pl-3 text-gray-700 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
|
||||
>Contact</a
|
||||
>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
19
frontend/src/components/SpinnerIcon.vue
Normal file
19
frontend/src/components/SpinnerIcon.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts" setup></script>
|
||||
<template>
|
||||
<svg
|
||||
role="status"
|
||||
class="inline mr-3 w-4 h-4 animate-spin"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="#E5E7EB"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
43
frontend/src/hub/defaults.ts
Normal file
43
frontend/src/hub/defaults.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { HttpHeaders, TBinaryProtocol, TCompactProtocol, TJSONProtocol } from 'thrift';
|
||||
import hub, { ClientOptions } from '.';
|
||||
import { ServiceType } from './service_type';
|
||||
import { HubHeader } from './hub';
|
||||
|
||||
let defaultHubUrl: URL;
|
||||
let hubSolveMethod = 'UNRESOLVED';
|
||||
try {
|
||||
defaultHubUrl = new URL(import.meta.env.VITE_HUB_URL || window.location.href);
|
||||
hubSolveMethod = import.meta.env.VITE_HUB_URL ? 'ENV' : 'BROWSER';
|
||||
} catch {
|
||||
defaultHubUrl = new URL(window.location.href);
|
||||
hubSolveMethod = 'BROWSER2';
|
||||
}
|
||||
console.log(`[hub] url ${defaultHubUrl} ${hubSolveMethod}`);
|
||||
|
||||
export const defaultHost = defaultHubUrl.hostname;
|
||||
export const defaultPort =
|
||||
parseInt(defaultHubUrl.port) || (window.location.protocol === 'https:' ? 443 : 80);
|
||||
export const defaultConfig: ClientOptions = {
|
||||
host: defaultHost,
|
||||
port: defaultPort,
|
||||
https: defaultHubUrl.protocol.indexOf('https') !== -1,
|
||||
path: defaultHubUrl.pathname + defaultHubUrl.search,
|
||||
protocol: TCompactProtocol,
|
||||
headers: ((): HttpHeaders => {
|
||||
const headers = {
|
||||
...HubHeader,
|
||||
'Content-Type': 'application/x-thrift; charset=utf-8; protocol=TCOMPACT',
|
||||
};
|
||||
return headers;
|
||||
})(),
|
||||
};
|
||||
console.log(`[hub] default config:`, defaultConfig);
|
||||
|
||||
export const defaultClientOptions = new Map<ServiceType, ClientOptions>([
|
||||
[
|
||||
ServiceType.BizCore,
|
||||
{
|
||||
...defaultConfig,
|
||||
},
|
||||
],
|
||||
]);
|
||||
36
frontend/src/hub/hub.ts
Normal file
36
frontend/src/hub/hub.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { ThriftClient } from './xhr_client';
|
||||
import /* * as */Core from '@thriftgen/CoreService';
|
||||
import { ServiceType } from './service_type';
|
||||
import { ClientOptions } from '.';
|
||||
import { defaultClientOptions } from './defaults';
|
||||
|
||||
export const HubClientVersion = '1.0.0';
|
||||
export const HeaderKeyHubVersion = 'x-hub-version';
|
||||
export const HeaderKeyHubEnv = 'x-hub-env';
|
||||
export const HeaderKeyHubApp = 'x-hub-app';
|
||||
|
||||
export const CookieKeyTrackToken = 'trk_token';
|
||||
export const HeaderKeyTrackToken = 'x-hub-track-token';
|
||||
|
||||
export const CookieKeyAuthToken = 'auth_token';
|
||||
export const HeaderKeyAuthToken = 'x-hub-auth-token';
|
||||
|
||||
export const HubHeader = {
|
||||
[HeaderKeyHubVersion]: HubClientVersion,
|
||||
[HeaderKeyHubEnv]: '*',
|
||||
[HeaderKeyHubApp]: 'global',
|
||||
};
|
||||
|
||||
export function defaultHubInit(
|
||||
opts: Map<ServiceType, ClientOptions> = defaultClientOptions,
|
||||
): ThriftHub {
|
||||
console.log('[hub] init');
|
||||
return new ThriftHub(opts);
|
||||
}
|
||||
|
||||
export class ThriftHub {
|
||||
BizCore: ThriftClient<Core.Client>;
|
||||
constructor(opts: Map<ServiceType, ClientOptions>) {
|
||||
this.BizCore = new ThriftClient(Core.Client, opts.get(ServiceType.BizCore)!);
|
||||
}
|
||||
}
|
||||
28
frontend/src/hub/index.ts
Normal file
28
frontend/src/hub/index.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import {
|
||||
createXHRClient,
|
||||
createXHRConnection,
|
||||
TBinaryProtocol,
|
||||
TJSONProtocol,
|
||||
XHRConnection,
|
||||
type ConnectOptions,
|
||||
} from 'thrift';
|
||||
import { App, inject } from 'vue';
|
||||
import { defaultClientOptions } from './defaults';
|
||||
import { defaultHubInit, ThriftHub } from './hub';
|
||||
import { ServiceType } from './service_type';
|
||||
|
||||
export type ClientOptions = ConnectOptions & {
|
||||
host: string;
|
||||
port: number;
|
||||
};
|
||||
|
||||
const hubProvideKey = 'thrift_hub';
|
||||
export function useHub(): ThriftHub {
|
||||
return inject(hubProvideKey) || defaultHubInit();
|
||||
}
|
||||
|
||||
export default {
|
||||
install: (app: App, opts: Map<ServiceType, ClientOptions> = defaultClientOptions) => {
|
||||
app.provide(hubProvideKey, defaultHubInit(opts));
|
||||
},
|
||||
};
|
||||
3
frontend/src/hub/service_type.ts
Normal file
3
frontend/src/hub/service_type.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export enum ServiceType {
|
||||
BizCore = 'biz_core',
|
||||
}
|
||||
80
frontend/src/hub/xhr_client.ts
Normal file
80
frontend/src/hub/xhr_client.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { getCookie, setCookie } from '@/utils/cookie';
|
||||
import {
|
||||
createClient,
|
||||
createXHRClient,
|
||||
createXHRConnection,
|
||||
TClientConstructor,
|
||||
XHRConnection,
|
||||
} from 'thrift';
|
||||
import { computed } from 'vue';
|
||||
import { ClientOptions } from '.';
|
||||
import { CookieKeyAuthToken, CookieKeyTrackToken, HeaderKeyAuthToken, HeaderKeyTrackToken } from './hub';
|
||||
|
||||
export function initXHRClient<TClient>(
|
||||
clientCtor: TClientConstructor<TClient>,
|
||||
opts: ClientOptions,
|
||||
): TClient {
|
||||
const { host, port, ...addOpts } = opts;
|
||||
const conn = createXHRConnection(host, port, { ...addOpts });
|
||||
const client = createXHRClient(clientCtor, conn);
|
||||
return client;
|
||||
}
|
||||
|
||||
export class ThriftClient<TClient> {
|
||||
opts: ClientOptions;
|
||||
conn: XHRConnection;
|
||||
client: TClient;
|
||||
constructor(clientCtor: TClientConstructor<TClient>, opts: ClientOptions) {
|
||||
this.opts = opts;
|
||||
this.conn = createXHRConnection(this.opts.host, this.opts.port, {
|
||||
...this.opts,
|
||||
});
|
||||
const originalGetXmlHttpRequestObject = this.conn.getXmlHttpRequestObject;
|
||||
this.conn.getXmlHttpRequestObject = () => {
|
||||
const xreq = originalGetXmlHttpRequestObject();
|
||||
xreq.responseType = 'arraybuffer';
|
||||
return xreq;
|
||||
};
|
||||
|
||||
const originalFlush = this.conn.flush;
|
||||
this.conn.flush = () => {
|
||||
const xreq = this.conn.getXmlHttpRequestObject();
|
||||
xreq.overrideMimeType('application/x-thrift');
|
||||
xreq.onreadystatechange = () => {
|
||||
if (xreq.readyState == 4 && xreq.status == 200) {
|
||||
// if (xreq.getAllResponseHeaders().indexOf(HeaderKeyTrackToken) >= 0) {
|
||||
// const token = xreq.getResponseHeader(HeaderKeyTrackToken);
|
||||
// if (token) {
|
||||
// console.log('got trk token:', token);
|
||||
// setCookie(CookieKeyTrackToken, token);
|
||||
// }
|
||||
// }
|
||||
this.conn.setRecvBuffer(xreq.response);
|
||||
}
|
||||
};
|
||||
const self: any = this.conn;
|
||||
xreq.open('POST', self.url, true);
|
||||
Object.keys(this.conn.headers).forEach(function (headerKey) {
|
||||
xreq.setRequestHeader(headerKey, self.headers[headerKey]);
|
||||
});
|
||||
|
||||
const trkToken = getCookie(CookieKeyTrackToken);
|
||||
if (trkToken) {
|
||||
console.log('use track token:', trkToken);
|
||||
xreq.setRequestHeader(HeaderKeyTrackToken, trkToken);
|
||||
}
|
||||
const authToken = getCookie(CookieKeyAuthToken);
|
||||
if (authToken) {
|
||||
console.log('use auth token:', authToken);
|
||||
xreq.setRequestHeader(HeaderKeyAuthToken, authToken);
|
||||
}
|
||||
try {
|
||||
xreq.send(this.conn.send_buf);
|
||||
} catch (e) {
|
||||
console.error('xhr client:', e);
|
||||
}
|
||||
};
|
||||
|
||||
this.client = createXHRClient(clientCtor, this.conn);
|
||||
}
|
||||
}
|
||||
25
frontend/src/main.ts
Normal file
25
frontend/src/main.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import 'vite/modulepreload-polyfill';
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import './style.css';
|
||||
import App from './App.vue';
|
||||
import hub from '@/hub';
|
||||
import router from './router';
|
||||
|
||||
import Vue3PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
|
||||
import Vue3EasyDataTable from 'vue3-easy-data-table';
|
||||
import 'vue3-easy-data-table/dist/style.css';
|
||||
|
||||
import 'flowbite';
|
||||
// import 'flowbite-vue';
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(hub); // client hub
|
||||
app.use(router);
|
||||
app.use(createPinia());
|
||||
|
||||
app.use(Vue3PerfectScrollbar);
|
||||
app.component('EasyDataTable', Vue3EasyDataTable);
|
||||
|
||||
app.mount('#app');
|
||||
24
frontend/src/router/index.ts
Normal file
24
frontend/src/router/index.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import core from '@/views/core';
|
||||
import error from '@/views/error';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
export type NavbarLinkMeta = {
|
||||
caption: string;
|
||||
classList?: string;
|
||||
priority?: number;
|
||||
};
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
requiresAuth?: boolean;
|
||||
displayNavigationBar?: boolean;
|
||||
navbarLink?: NavbarLinkMeta;
|
||||
}
|
||||
}
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [...core.routes(), ...error.routes()],
|
||||
});
|
||||
|
||||
export default router;
|
||||
55
frontend/src/stores/alert.ts
Normal file
55
frontend/src/stores/alert.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { defineStore } from 'pinia';
|
||||
|
||||
export enum AlertType {
|
||||
Error,
|
||||
Warning,
|
||||
Success,
|
||||
}
|
||||
|
||||
export type Alert = {
|
||||
show: boolean;
|
||||
className: string;
|
||||
title: string;
|
||||
description: string;
|
||||
type: AlertType;
|
||||
};
|
||||
|
||||
const defaultData = () =>
|
||||
<Alert>{
|
||||
show: false,
|
||||
className: '',
|
||||
title: '',
|
||||
description: '',
|
||||
type: AlertType.Error,
|
||||
};
|
||||
|
||||
export const useAlert = defineStore({
|
||||
id: 'alert',
|
||||
state: () => {
|
||||
return defaultData();
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
hide() {
|
||||
this.show = false;
|
||||
},
|
||||
showContent(
|
||||
title: string,
|
||||
description: string,
|
||||
opt?: { className?: string; type?: AlertType },
|
||||
) {
|
||||
Object.assign(this, defaultData());
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.show = true;
|
||||
if (opt) {
|
||||
if (opt.className) {
|
||||
this.className = opt.className;
|
||||
}
|
||||
if (opt.type) {
|
||||
this.type = opt.type;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
73
frontend/src/stores/auth.ts
Normal file
73
frontend/src/stores/auth.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { defineStore } from 'pinia';
|
||||
|
||||
import /* * as */structs from '@thriftgen/structs_types';
|
||||
import { deleteCookie, getCookie, setCookie } from '@/utils/cookie';
|
||||
import { CookieKeyAuthToken, CookieKeyTrackToken } from '@/hub/hub';
|
||||
import { RemovableRef, useLocalStorage } from '@vueuse/core';
|
||||
|
||||
export type Auth = {
|
||||
trkToken: string;
|
||||
token: string;
|
||||
profile?: structs.User;
|
||||
};
|
||||
|
||||
type AuthState = {
|
||||
trkToken: RemovableRef<string>;
|
||||
token: RemovableRef<string>;
|
||||
profile?: structs.User;
|
||||
};
|
||||
|
||||
export function isValidToken(token: string): boolean {
|
||||
return token != '' && token != undefined && token != 'undefined';
|
||||
}
|
||||
|
||||
export const useAuth = defineStore({
|
||||
id: 'user',
|
||||
state: () =>
|
||||
<AuthState>{
|
||||
trkToken: useLocalStorage('trkToken', ''),
|
||||
token: useLocalStorage('token', ''),
|
||||
profile: undefined,
|
||||
},
|
||||
getters: {
|
||||
hasProfile(state): boolean {
|
||||
return state.profile != null;
|
||||
},
|
||||
hasToken(state): boolean {
|
||||
return isValidToken(this.trackToken);
|
||||
},
|
||||
isLoggedIn(state): boolean {
|
||||
return this.hasToken && this.hasProfile;
|
||||
},
|
||||
trackToken(state): string {
|
||||
return state.trkToken;
|
||||
},
|
||||
authToken(state): string {
|
||||
return state.token;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setTrackToken(token?: string) {
|
||||
if (!token) return;
|
||||
console.log('set track token:', token);
|
||||
setCookie(CookieKeyTrackToken, token);
|
||||
this.trkToken = token;
|
||||
},
|
||||
setAuthToken(token?: string) {
|
||||
if (!token) return;
|
||||
console.log('set auth token:', token);
|
||||
setCookie(CookieKeyAuthToken, token);
|
||||
this.token = token;
|
||||
},
|
||||
setProfile(profile: structs.User) {
|
||||
this.profile = profile;
|
||||
},
|
||||
logout() {
|
||||
deleteCookie(CookieKeyAuthToken);
|
||||
deleteCookie(CookieKeyTrackToken);
|
||||
this.profile = undefined;
|
||||
this.token = '';
|
||||
this.trkToken = '';
|
||||
},
|
||||
},
|
||||
});
|
||||
25
frontend/src/stores/theme.ts
Normal file
25
frontend/src/stores/theme.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useTheme = defineStore({
|
||||
id: 'theme',
|
||||
state: () => ({
|
||||
_darkTheme: false,
|
||||
_navigationBarVisibility: true,
|
||||
}),
|
||||
getters: {
|
||||
isDarkThemeEnabled(state): boolean {
|
||||
return state._darkTheme;
|
||||
},
|
||||
navigationBarVisibility(state): boolean {
|
||||
return state._navigationBarVisibility
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
toggleDarkTheme() {
|
||||
this._darkTheme = !this._darkTheme;
|
||||
},
|
||||
setNavigationBarVisibility(visibility: boolean): void {
|
||||
this._navigationBarVisibility = visibility;
|
||||
},
|
||||
},
|
||||
});
|
||||
4
frontend/src/style.css
Normal file
4
frontend/src/style.css
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
33
frontend/src/utils/cookie.ts
Normal file
33
frontend/src/utils/cookie.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* General utils for managing cookies in Typescript.
|
||||
*/
|
||||
export function setCookie(name: string, val: string) {
|
||||
const date = new Date();
|
||||
const value = val;
|
||||
|
||||
// Set it expire in 7 days
|
||||
date.setTime(date.getTime() + 7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
// Set it
|
||||
document.cookie = name + '=' + value + '; expires=' + date.toUTCString() + '; path=/';
|
||||
}
|
||||
|
||||
export function getCookie(name: string): string | undefined {
|
||||
const value = '; ' + document.cookie;
|
||||
const parts = value.split('; ' + name + '=');
|
||||
|
||||
if (parts.length == 2) {
|
||||
return parts.pop()?.split(';').shift();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function deleteCookie(name: string) {
|
||||
const date = new Date();
|
||||
|
||||
// Set it expire in -1 days
|
||||
date.setTime(date.getTime() + -1 * 24 * 60 * 60 * 1000);
|
||||
|
||||
// Set it
|
||||
document.cookie = name + '=; expires=' + date.toUTCString() + '; path=/';
|
||||
}
|
||||
501
frontend/src/views/core/AdminUserListView.vue
Normal file
501
frontend/src/views/core/AdminUserListView.vue
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
<script lang="ts" setup>
|
||||
import /* * as */structs from '@thriftgen/structs_types';
|
||||
import /* * as */exceptions from '@thriftgen/exceptions_types';
|
||||
import { useHub } from '@/hub';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import type { Header, Item, ServerOptions, SortType } from 'vue3-easy-data-table';
|
||||
import HeadingPage from './components/HeadingPage.vue';
|
||||
import ModalDialog from './components/ModalDialog.vue';
|
||||
import AlertBox from '@/components/AlertBox.vue';
|
||||
import { AlertType, useAlert } from '@/stores/alert';
|
||||
import { EyeIcon, EyeSlashIcon } from '@heroicons/vue/24/solid';
|
||||
import SpinnerIcon from '@/components/SpinnerIcon.vue';
|
||||
|
||||
const isLoading = ref(false);
|
||||
const hub = useHub();
|
||||
const alert = useAlert();
|
||||
|
||||
const sortBy: string[] = ['id', 'username'];
|
||||
const sortType: SortType[] = ['desc', 'asc'];
|
||||
const headers: Header[] = [
|
||||
// { text: '#', value: 'id', sortable: false },
|
||||
{ text: 'Username', value: 'username', sortable: false },
|
||||
{ text: 'Name', value: 'displayName' },
|
||||
{ text: 'Roles', value: 'roles' },
|
||||
{ text: 'Actions', value: 'actions' },
|
||||
];
|
||||
|
||||
const items = ref<Item[]>([]);
|
||||
const searchTerm = ref('');
|
||||
const serverItemsLength = ref(0);
|
||||
const serverOptions = ref<ServerOptions>({
|
||||
page: 1,
|
||||
rowsPerPage: 25,
|
||||
});
|
||||
const itemsSelected = ref<Item[]>([]);
|
||||
const hasSelectedItems = computed(() => itemsSelected.value.length > 0);
|
||||
const captionBulk = computed(() =>
|
||||
hasSelectedItems.value ? ` (${itemsSelected.value.length})` : '',
|
||||
);
|
||||
const searchField = ['Username', 'Name', 'Roles'];
|
||||
|
||||
async function fetchUsersToTable() {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
console.log('svr opt:', serverOptions);
|
||||
const { page, rowsPerPage } = serverOptions.value;
|
||||
|
||||
const pagination = new structs.Pagination({ page, rowsPerPage });
|
||||
const req = new structs.GetUserListRequest({
|
||||
pagination: pagination,
|
||||
searchTerm: searchTerm.value,
|
||||
});
|
||||
|
||||
const res = await hub.BizCore.client.getUserList(req);
|
||||
console.log('get users', res);
|
||||
items.value = res.users;
|
||||
serverItemsLength.value = res.totalUsers + 0;
|
||||
} catch (e: any) {
|
||||
if (e instanceof exceptions.CoreServicesException) {
|
||||
const alertVal = e.parameters['alert'];
|
||||
if (alertVal) {
|
||||
const alertData: structs.AlertInfo = JSON.parse(alertVal);
|
||||
alert.showContent(alertData.title, alertData.description);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
alert.showContent('Server Error', e.toString());
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteUser(user: structs.User) {
|
||||
console.log('delete', user);
|
||||
const users = [user];
|
||||
const userConfirmed = await showDeleteDialog(users);
|
||||
if (!userConfirmed) {
|
||||
return;
|
||||
}
|
||||
await doDeleteUsers(users);
|
||||
await fetchUsersToTable();
|
||||
return;
|
||||
}
|
||||
|
||||
async function manageRoles(user: structs.User) {
|
||||
console.log('manage role', user);
|
||||
return;
|
||||
}
|
||||
|
||||
async function createUser() {
|
||||
console.log('create user');
|
||||
showCreateUserDialog(async (req) => {
|
||||
try {
|
||||
const resp = await hub.BizCore.client.createUser(req);
|
||||
console.log('create user resp', resp);
|
||||
} catch (e: any) {
|
||||
if (e instanceof exceptions.CoreServicesException) {
|
||||
const alertVal = e.parameters['alert'];
|
||||
if (alertVal) {
|
||||
const alertData: structs.AlertInfo = JSON.parse(alertVal);
|
||||
alert.showContent(alertData.title, alertData.description);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
alert.showContent('Server Error', e.toString());
|
||||
} finally {
|
||||
hideDialog();
|
||||
fetchUsersToTable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteUserSelected() {
|
||||
if (!hasSelectedItems.value) {
|
||||
return;
|
||||
}
|
||||
const users = itemsSelected.value as structs.User[];
|
||||
const userConfirmed = await showDeleteDialog(users);
|
||||
if (!userConfirmed) {
|
||||
return;
|
||||
}
|
||||
await doDeleteUsers(users);
|
||||
await fetchUsersToTable();
|
||||
itemsSelected.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
async function manageRolesSelected() {
|
||||
if (!hasSelectedItems.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('selected ', itemsSelected.value.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// initial load
|
||||
fetchUsersToTable();
|
||||
|
||||
watch(serverOptions, () => {
|
||||
fetchUsersToTable();
|
||||
});
|
||||
watch(searchTerm, (value) => {
|
||||
fetchUsersToTable();
|
||||
});
|
||||
|
||||
/* RPC calls */
|
||||
|
||||
async function doDeleteUsers(users: structs.User[]) {
|
||||
const req = new structs.DeleteUsersRequest({
|
||||
userIds: users.map((u) => u.id).filter((id) => id),
|
||||
});
|
||||
try {
|
||||
const resp = await hub.BizCore.client.deleteUsers(req);
|
||||
console.log('delete user result', resp);
|
||||
const { success: deleted, ignored } = resp;
|
||||
alert.showContent(
|
||||
'Delete user',
|
||||
`${deleted.length > 0 ? deleted.length + ' deleted' + (ignored.length > 0 ? ', ' : '') : ''}${
|
||||
ignored.length > 0 ? ignored.length + ' ignored' : ''
|
||||
}.`,
|
||||
{
|
||||
type: AlertType.Success,
|
||||
},
|
||||
);
|
||||
} catch (e: any) {
|
||||
if (e instanceof exceptions.CoreServicesException) {
|
||||
const alertVal = e.parameters['alert'];
|
||||
if (alertVal) {
|
||||
const alertData: structs.AlertInfo = JSON.parse(alertVal);
|
||||
alert.showContent(alertData.title, alertData.description);
|
||||
return;
|
||||
}
|
||||
}
|
||||
alert.showContent('Server Error', e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal script */
|
||||
|
||||
const modalView = ref('');
|
||||
const modalContext = ref({} as any);
|
||||
const modalActionCb = ref<(() => void)[]>([]);
|
||||
const modalWithClose = ref(false);
|
||||
const isModalShow = ref(false);
|
||||
function showDialog(
|
||||
view: string,
|
||||
ctx: any,
|
||||
opt?: {
|
||||
withClose?: boolean;
|
||||
actions?: ((resolve: (value: boolean | PromiseLike<boolean>) => void) => void)[];
|
||||
},
|
||||
): Promise<boolean> {
|
||||
isModalShow.value = true;
|
||||
modalWithClose.value = (opt || {}).withClose || false;
|
||||
modalView.value = view;
|
||||
modalContext.value = ctx;
|
||||
return new Promise((resolve) => {
|
||||
const actions = ((opt || {}).actions || []).map((act) => () => {
|
||||
isModalShow.value = false; // TODO: make this optional
|
||||
act(resolve);
|
||||
});
|
||||
modalActionCb.value = actions;
|
||||
});
|
||||
}
|
||||
function hideDialog() {
|
||||
isModalShow.value = false;
|
||||
}
|
||||
|
||||
function showDeleteDialog(users: structs.User[]): Promise<boolean> {
|
||||
return showDialog('confirm-delete', users, {
|
||||
withClose: false,
|
||||
actions: [
|
||||
(resolve) => {
|
||||
resolve(true);
|
||||
},
|
||||
(resolve) => {
|
||||
resolve(false);
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
function showCreateUserDialog(
|
||||
actionCb: (req: structs.CreateUserRequest) => Promise<void>,
|
||||
): Promise<boolean> {
|
||||
const state = { passwordShow: ref(false), isLoading: ref(false) };
|
||||
const data = {
|
||||
username: ref(''),
|
||||
password: ref(''),
|
||||
displayName: ref(''),
|
||||
};
|
||||
const formHandler = async (ev: Event) => {
|
||||
ev.preventDefault();
|
||||
const payload = {
|
||||
username: data.username.value,
|
||||
password: data.password.value,
|
||||
displayName: data.displayName.value,
|
||||
};
|
||||
// loading
|
||||
state.isLoading.value = true;
|
||||
const user = new structs.User();
|
||||
Object.assign(user, payload);
|
||||
try {
|
||||
await actionCb(
|
||||
new structs.CreateUserRequest({
|
||||
user: user,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
state.isLoading.value = false;
|
||||
}
|
||||
};
|
||||
const passwordToggle = () => {
|
||||
state.passwordShow.value = !state.passwordShow.value;
|
||||
};
|
||||
return showDialog(
|
||||
'create-user',
|
||||
{
|
||||
state,
|
||||
data,
|
||||
formHandler,
|
||||
passwordToggle,
|
||||
},
|
||||
{
|
||||
withClose: true,
|
||||
actions: [],
|
||||
},
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="px-2 sm:px-4 py-2.5">
|
||||
<div class="container mx-auto">
|
||||
<HeadingPage>List Users</HeadingPage>
|
||||
|
||||
<AlertBox />
|
||||
|
||||
<!-- control -->
|
||||
<div class="mt-2">
|
||||
<a
|
||||
href="#"
|
||||
class="text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-2.5 py-2 mr-2 mb-2 dark:bg-green-600 dark:hover:bg-green-700 focus:outline-none dark:focus:ring-green-800"
|
||||
@click="createUser()"
|
||||
>Create User</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
:disabled="!hasSelectedItems"
|
||||
:class="[
|
||||
{ 'cursor-not-allowed': !hasSelectedItems },
|
||||
'text-white bg-cyan-700 hover:bg-cyan-800 focus:ring-4 focus:ring-cyan-300 font-medium rounded-lg text-sm px-2.5 py-2 mr-2 mb-2 dark:bg-cyan-600 dark:hover:bg-cyan-700 focus:outline-none dark:focus:ring-cyan-800',
|
||||
]"
|
||||
@click="manageRolesSelected()"
|
||||
>Manage Roles{{ captionBulk }}</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
:disabled="!hasSelectedItems"
|
||||
:class="[
|
||||
{ 'cursor-not-allowed': !hasSelectedItems },
|
||||
'text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-2.5 py-2 mr-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800',
|
||||
]"
|
||||
@click="deleteUserSelected"
|
||||
>Delete{{ captionBulk }}</a
|
||||
>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search user/name"
|
||||
v-model="searchTerm"
|
||||
:class="[
|
||||
'peer',
|
||||
'px-2.5 py-2',
|
||||
//
|
||||
'w-full block mt-4',
|
||||
'md:w-auto md:inline-block md:mt-0', // mobile
|
||||
// 'px-1 pb-1 pt-1 text-sm',
|
||||
'bg-transparent rounded-lg border-1',
|
||||
'text-sm text-gray-900',
|
||||
'border-gray-300 appearance-none',
|
||||
|
||||
'dark:text-white dark:border-gray-600',
|
||||
'focus:outline-none focus:ring-0 focus:border-blue-600',
|
||||
'dark:focus:border-blue-500',
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- table -->
|
||||
<div class="mt-5">
|
||||
<EasyDataTable
|
||||
buttons-pagination
|
||||
v-model:server-options="serverOptions"
|
||||
v-model:items-selected="itemsSelected"
|
||||
:server-items-length="serverItemsLength"
|
||||
:loading="isLoading"
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:sort-by="sortBy"
|
||||
:sort-type="sortType"
|
||||
:search-field="searchField"
|
||||
:search-value="searchTerm"
|
||||
>
|
||||
<template #item-roles="{ roles }">
|
||||
<span v-if="roles.length > 0">
|
||||
<a
|
||||
:href="'/admin/role/' + role.id"
|
||||
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-xs px-1 py-1 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
|
||||
v-for="(role, i) in roles"
|
||||
:key="i"
|
||||
>
|
||||
{{ role.displayName }}
|
||||
</a>
|
||||
</span>
|
||||
<span v-else><i>No roles</i></span>
|
||||
</template>
|
||||
<template #item-actions="user">
|
||||
<a
|
||||
href="#"
|
||||
class="text-white bg-cyan-700 hover:bg-cyan-800 focus:ring-4 focus:ring-cyan-300 font-medium rounded-lg text-xs px-2 py-1.5 mr-2 mb-2 dark:bg-cyan-600 dark:hover:bg-cyan-700 focus:outline-none dark:focus:ring-cyan-800"
|
||||
@click="manageRoles(user)"
|
||||
>Manage Roles</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-xs px-2 py-1.5 mr-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800"
|
||||
@click="deleteUser(user)"
|
||||
>Delete</a
|
||||
>
|
||||
</template>
|
||||
</EasyDataTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal -->
|
||||
<ModalDialog
|
||||
@on-show="() => (isModalShow = true)"
|
||||
@on-hide="() => (isModalShow = false)"
|
||||
:show="isModalShow"
|
||||
:slot-view-name="modalView"
|
||||
:with-close-button="modalWithClose"
|
||||
>
|
||||
<template #view-confirm-delete>
|
||||
<div class="p-6 text-center">
|
||||
<h3 class="mb-5 text-lg font-normal">
|
||||
Are you sure want to delete this user{{ modalContext.length > 1 ? 's' : '' }}? <br />{{
|
||||
modalContext.length <= 3
|
||||
? modalContext.map((x: structs.User) => x.username).join(', ')
|
||||
: `${modalContext.length} users`
|
||||
}}
|
||||
</h3>
|
||||
<button
|
||||
class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center mr-2"
|
||||
@click="modalActionCb[0]"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
@click="modalActionCb[1]"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #view-create-user>
|
||||
<div class="py-6 px-6 lg:px-8">
|
||||
<!-- heading -->
|
||||
<h3 class="mb-4 text-xl font-medium text-gray-900 dark:text-white">Create user</h3>
|
||||
<!-- body -->
|
||||
<form class="space-y-6" @submit="modalContext.formHandler">
|
||||
<!-- username -->
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="modalContext.data.displayName"
|
||||
type="text"
|
||||
id="txt_display_name"
|
||||
:disabled="isLoading"
|
||||
:class="[
|
||||
{ disabled: modalContext.state.isLoading },
|
||||
'block px-2.5 pb-2.5 pt-4 w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer',
|
||||
]"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
for="txt_display_name"
|
||||
class="absolute text-sm text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] bg-white dark:bg-gray-900 px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
|
||||
>Display Name</label
|
||||
>
|
||||
</div>
|
||||
<!-- username -->
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="modalContext.data.username"
|
||||
type="text"
|
||||
id="txt_username"
|
||||
:disabled="modalContext.state.isLoading"
|
||||
:class="[
|
||||
{ disabled: isLoading },
|
||||
'block px-2.5 pb-2.5 pt-4 w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer',
|
||||
]"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
for="txt_username"
|
||||
class="absolute text-sm text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] bg-white dark:bg-gray-900 px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
|
||||
>Username</label
|
||||
>
|
||||
</div>
|
||||
<!-- password -->
|
||||
<div class="flex relative">
|
||||
<input
|
||||
v-model="modalContext.data.password"
|
||||
:type="modalContext.state.passwordShow ? 'text' : 'password'"
|
||||
id="txt_password"
|
||||
:disabled="isLoading"
|
||||
:class="[
|
||||
{ disabled: modalContext.state.isLoading },
|
||||
'block px-2.5 pb-2.5 pt-4 w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer',
|
||||
]"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
for="txt_password"
|
||||
class="absolute text-sm text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] bg-white dark:bg-gray-900 px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
|
||||
>Password</label
|
||||
>
|
||||
<div class="absolute right-1.5 bottom-1.5">
|
||||
<div href="#" @click="modalContext.passwordToggle" class="p-2">
|
||||
<EyeIcon
|
||||
v-if="!modalContext.state.passwordShow"
|
||||
class="w-5 h-5 text-sm text-gray-900"
|
||||
/>
|
||||
<EyeSlashIcon v-else class="w-5 h-5 text-sm text-gray-900" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
>
|
||||
<span v-if="!modalContext.state.isLoading">Create</span>
|
||||
<span v-else>
|
||||
<SpinnerIconVue class="text-white" />
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<template #view-update-user>
|
||||
<h1>Update user</h1>
|
||||
</template>
|
||||
<template #view-manager-users-roles>
|
||||
<h1>Manage users roles</h1>
|
||||
</template>
|
||||
</ModalDialog>
|
||||
</template>
|
||||
16
frontend/src/views/core/HomeView.vue
Normal file
16
frontend/src/views/core/HomeView.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts" setup>
|
||||
import { useAuth } from '@/stores/auth';
|
||||
import { MagnifyingGlassIcon } from '@heroicons/vue/24/outline';
|
||||
import HeadingPage from './components/HeadingPage.vue';
|
||||
|
||||
const auth = useAuth();
|
||||
</script>
|
||||
<template>
|
||||
<div class="px-2 sm:px-4 py-2.5">
|
||||
<div class="container mx-auto">
|
||||
<HeadingPage
|
||||
>Welcome<span v-if="auth.profile">, {{ auth.profile?.displayName }}</span></HeadingPage
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
80
frontend/src/views/core/LoginView.vue
Normal file
80
frontend/src/views/core/LoginView.vue
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<script lang="ts" setup>
|
||||
import LoginCard from '@/views/core/components/LoginCard.vue';
|
||||
import NavigationBar from '@/components/NavigationBar.vue';
|
||||
import { useAuth } from '@/stores/auth';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import /* * as */structs from '@thriftgen/structs_types';
|
||||
import /* * as */exceptions from '@thriftgen/exceptions_types';
|
||||
import { useAlert } from '@/stores/alert';
|
||||
import { computed, watch } from 'vue';
|
||||
|
||||
const auth = useAuth();
|
||||
const alert = useAlert();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const nextRedirect = route.redirectedFrom;
|
||||
|
||||
const isLoggedIn = computed(() => auth.isLoggedIn);
|
||||
if (isLoggedIn.value) {
|
||||
onAuthStateChange(isLoggedIn.value);
|
||||
}
|
||||
|
||||
watch(isLoggedIn, (value) => {
|
||||
onAuthStateChange(value);
|
||||
});
|
||||
|
||||
function onAuthStateChange(value: boolean) {
|
||||
console.log('auth state changed', value);
|
||||
if (value) {
|
||||
if (nextRedirect) {
|
||||
router.push(nextRedirect.fullPath);
|
||||
} else {
|
||||
router.push('/');
|
||||
}
|
||||
console.log(window.history.length);
|
||||
// if (window.history.length > 2) {
|
||||
// router.back();
|
||||
// } else {
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
function onInputChange() {
|
||||
alert.hide();
|
||||
}
|
||||
|
||||
async function onLoginSuccess(resp: structs.LoginResponse) {
|
||||
if (resp.trkToken) {
|
||||
auth.setTrackToken(resp.trkToken);
|
||||
}
|
||||
if (resp.token) {
|
||||
auth.setAuthToken(resp.token);
|
||||
}
|
||||
if (resp.profile) {
|
||||
auth.setProfile(resp.profile);
|
||||
}
|
||||
// console.log('success check', auth.trkToken, auth.authToken);
|
||||
router.push('/');
|
||||
}
|
||||
|
||||
function onLoginFailed(e: any) {
|
||||
if (e instanceof exceptions.CoreServicesException) {
|
||||
const alertVal = e.parameters['alert'];
|
||||
if (alertVal) {
|
||||
const alertData: structs.AlertInfo = JSON.parse(alertVal);
|
||||
alert.showContent(alertData.title, alertData.description);
|
||||
return;
|
||||
}
|
||||
}
|
||||
alert.showContent('Server Error', e.toString());
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="mt-11 flex items-center justify-center">
|
||||
<LoginCard
|
||||
@on-login-success="onLoginSuccess"
|
||||
@on-login-failed="onLoginFailed"
|
||||
@on-input-change="onInputChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
358
frontend/src/views/core/UserWajibPajakListView.vue
Normal file
358
frontend/src/views/core/UserWajibPajakListView.vue
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
<script lang="ts" setup>
|
||||
import AlertBox from '@/components/AlertBox.vue';
|
||||
import { useHub } from '@/hub';
|
||||
import /* * as */structs from '@thriftgen/structs_types';
|
||||
import /* * as */exceptions from '@thriftgen/exceptions_types';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Header, Item, ServerOptions, SortType } from 'vue3-easy-data-table';
|
||||
import HeadingPage from './components/HeadingPage.vue';
|
||||
import { useAlert } from '@/stores/alert';
|
||||
import ModalDialog from './components/ModalDialog.vue';
|
||||
import { join } from 'path';
|
||||
|
||||
const hub = useHub();
|
||||
const alert = useAlert();
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const headers: Header[] = [
|
||||
{ text: 'NPWP', value: 'profile.npwp' },
|
||||
{ text: 'Name', value: 'profile.displayName' },
|
||||
{ text: 'Address', value: 'profile.address' },
|
||||
{ text: 'Actions', value: 'actions' },
|
||||
];
|
||||
const items = ref<Item[]>([]);
|
||||
const sortBy: string[] = ['name'];
|
||||
const sortType: SortType[] = ['desc', 'asc'];
|
||||
const serverItemsLength = ref(0);
|
||||
const serverOptions = ref<ServerOptions>({
|
||||
page: 1,
|
||||
rowsPerPage: 25,
|
||||
});
|
||||
const itemsSelected = ref<Item[]>([]);
|
||||
const hasSelectedItems = computed(() => itemsSelected.value.length > 0);
|
||||
|
||||
const searchField = ['Name'];
|
||||
const searchTerm = ref('');
|
||||
const ownershipStatus = ref(structs.WajibPajakOwnership.OWNED);
|
||||
|
||||
async function fetchWajibPajakToTable() {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
console.log('svr opt:', serverOptions);
|
||||
const { page, rowsPerPage } = serverOptions.value;
|
||||
|
||||
const pagination = new structs.Pagination({ page, rowsPerPage });
|
||||
|
||||
const req = new structs.GetWajibPajakListRequest({
|
||||
pagination: pagination,
|
||||
searchTerm: searchTerm.value,
|
||||
ownership: ownershipStatus.value,
|
||||
});
|
||||
const resp = await hub.BizCore.client.getWajibPajakList(req);
|
||||
console.log('get wps', resp);
|
||||
items.value = resp.wajibPajakList;
|
||||
serverItemsLength.value = resp.totalWajibPajak + 0;
|
||||
} catch (e: any) {
|
||||
if (e instanceof exceptions.CoreServicesException) {
|
||||
const alertVal = e.parameters['alert'];
|
||||
if (alertVal) {
|
||||
const alertData: structs.AlertInfo = JSON.parse(alertVal);
|
||||
alert.showContent(alertData.title, alertData.description);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
alert.showContent('Server Error', e.toString());
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
fetchWajibPajakToTable();
|
||||
watch(serverOptions, () => {
|
||||
fetchWajibPajakToTable();
|
||||
});
|
||||
watch(searchTerm, (value) => {
|
||||
fetchWajibPajakToTable();
|
||||
});
|
||||
watch(ownershipStatus, (value) => {
|
||||
fetchWajibPajakToTable();
|
||||
});
|
||||
|
||||
/* Event callback */
|
||||
async function createWp() {
|
||||
console.log('create wp');
|
||||
showCreateWpDialog(async (req) => {
|
||||
try {
|
||||
const resp = await hub.BizCore.client.createWajibPajak(req);
|
||||
console.log('create wp resp', resp);
|
||||
} catch (e: any) {
|
||||
if (e instanceof exceptions.CoreServicesException) {
|
||||
const alertVal = e.parameters['alert'];
|
||||
if (alertVal) {
|
||||
const alertData: structs.AlertInfo = JSON.parse(alertVal);
|
||||
alert.showContent(alertData.title, alertData.description);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
alert.showContent('Server Error', e.toString());
|
||||
} finally {
|
||||
hideDialog();
|
||||
fetchWajibPajakToTable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Modal script */
|
||||
const isModalShow = ref(false);
|
||||
const modalView = ref('');
|
||||
const modalWithClose = ref(false);
|
||||
const modalContext = ref({} as any);
|
||||
const modalActionCb = ref<(() => void)[]>([]);
|
||||
|
||||
function showDialog(
|
||||
view: string,
|
||||
ctx: any,
|
||||
opt?: {
|
||||
withClose?: boolean;
|
||||
actions?: ((resolve: (value: boolean | PromiseLike<boolean>) => void) => void)[];
|
||||
},
|
||||
): Promise<boolean> {
|
||||
isModalShow.value = true;
|
||||
modalWithClose.value = (opt || {}).withClose || false;
|
||||
modalView.value = view;
|
||||
modalContext.value = ctx;
|
||||
return new Promise((resolve) => {
|
||||
const actions = ((opt || {}).actions || []).map((act) => () => {
|
||||
isModalShow.value = false; // TODO: make this optional
|
||||
act(resolve);
|
||||
});
|
||||
modalActionCb.value = actions;
|
||||
});
|
||||
}
|
||||
function hideDialog() {
|
||||
isModalShow.value = false;
|
||||
}
|
||||
|
||||
function showCreateWpDialog(
|
||||
actionCb: (req: structs.CreateWajibPajakRequest) => Promise<void>,
|
||||
): Promise<boolean> {
|
||||
const state = { isLoading: ref(false) };
|
||||
const data = {
|
||||
npwp: ref(''),
|
||||
displayName: ref(''),
|
||||
address: ref(''),
|
||||
};
|
||||
const formHandler = async (ev: Event) => {
|
||||
ev.preventDefault();
|
||||
const payload = {
|
||||
npwp: data.npwp.value,
|
||||
displayName: data.displayName.value,
|
||||
address: data.address.value,
|
||||
};
|
||||
state.isLoading.value = true;
|
||||
const wp = new structs.WajibPajak();
|
||||
wp.profile = new structs.WajibPajakProfile(payload);
|
||||
try {
|
||||
await actionCb(
|
||||
new structs.CreateWajibPajakRequest({
|
||||
wajibPajak: wp,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
state.isLoading.value = false;
|
||||
}
|
||||
};
|
||||
return showDialog(
|
||||
'create-wp',
|
||||
{
|
||||
state,
|
||||
data,
|
||||
formHandler,
|
||||
},
|
||||
{ withClose: true },
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="px-2 sm:px-4 py-2.5">
|
||||
<div class="container mx-auto">
|
||||
<HeadingPage>List Wajib Pajak</HeadingPage>
|
||||
|
||||
<AlertBox />
|
||||
|
||||
<!-- control -->
|
||||
<div class="mt-2">
|
||||
<a
|
||||
href="#"
|
||||
@click="createWp"
|
||||
class="text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-2.5 py-2 mr-2 mb-2 dark:bg-green-600 dark:hover:bg-green-700 focus:outline-none dark:focus:ring-green-800"
|
||||
>Create Wajib Pajak</a
|
||||
>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search name"
|
||||
v-model="searchTerm"
|
||||
:class="[
|
||||
'peer',
|
||||
'px-2.5 py-2',
|
||||
//
|
||||
'w-full block mt-4',
|
||||
'md:w-auto md:inline-block md:mt-0', // mobile
|
||||
// 'px-1 pb-1 pt-1 text-sm',
|
||||
'bg-transparent rounded-lg border-1',
|
||||
'text-sm text-gray-900',
|
||||
'border-gray-300 appearance-none',
|
||||
|
||||
'dark:text-white dark:border-gray-600',
|
||||
'focus:outline-none focus:ring-0 focus:border-blue-600',
|
||||
'dark:focus:border-blue-500',
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- body -->
|
||||
<div class="mt-5">
|
||||
<ul
|
||||
class="flex flex-wrap text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:border-gray-700 dark:text-gray-400"
|
||||
>
|
||||
<li
|
||||
v-for="(tab, i) in [
|
||||
{
|
||||
caption: 'Owned',
|
||||
value: structs.WajibPajakOwnership.OWNED,
|
||||
},
|
||||
{
|
||||
caption: 'Role Access',
|
||||
value: structs.WajibPajakOwnership.ROLE,
|
||||
},
|
||||
]"
|
||||
:aria-current="tab.value == ownershipStatus ? 'page' : 'false'"
|
||||
:key="i"
|
||||
class="mr-2"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
@click="ownershipStatus = tab.value"
|
||||
:aria-current="tab.value == ownershipStatus ? 'page' : 'false'"
|
||||
:class="[
|
||||
'inline-block p-4 rounded-t-lg',
|
||||
'dark:hover:bg-gray-800',
|
||||
tab.value == ownershipStatus
|
||||
? 'active text-blue-600 bg-gray-100 dark:text-blue-500' //
|
||||
: 'hover:test-gray-600 hover:bg-gray-50 dark:hover:text-gray-300', //
|
||||
]"
|
||||
>{{ tab.caption }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<EasyDataTable
|
||||
buttons-pagination
|
||||
v-model:server-options="serverOptions"
|
||||
v-model:items-selected="itemsSelected"
|
||||
:server-items-length="serverItemsLength"
|
||||
:loading="isLoading"
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:sort-by="sortBy"
|
||||
:sort-type="sortType"
|
||||
:search-field="searchField"
|
||||
:search-value="searchTerm"
|
||||
>
|
||||
</EasyDataTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal -->
|
||||
<ModalDialog
|
||||
@on-show="() => (isModalShow = true)"
|
||||
@on-hide="() => (isModalShow = false)"
|
||||
:show="isModalShow"
|
||||
:slot-view-name="modalView"
|
||||
:with-close-button="modalWithClose"
|
||||
>
|
||||
<template #view-create-wp>
|
||||
<div class="py-6 px-6 lg:px-8">
|
||||
<!-- heading -->
|
||||
<h3 class="mb-4 text-xl font-medium text-gray-900 dark:text-white">Create Wajib Pajak</h3>
|
||||
<!-- body -->
|
||||
<form class="space-y-6" @submit="modalContext.formHandler">
|
||||
<!-- npwp -->
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="modalContext.data.npwp"
|
||||
type="text"
|
||||
id="txt_npwp"
|
||||
:disabled="isLoading"
|
||||
:class="[
|
||||
{ disabled: modalContext.state.isLoading },
|
||||
'block px-2.5 pb-2.5 pt-4 w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer',
|
||||
]"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
for="txt_npwp"
|
||||
class="absolute text-sm text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] bg-white dark:bg-gray-900 px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
|
||||
>NPWP</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- displayName -->
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="modalContext.data.displayName"
|
||||
type="text"
|
||||
id="txt_display_name"
|
||||
:disabled="isLoading"
|
||||
:class="[
|
||||
{ disabled: modalContext.state.isLoading },
|
||||
'block px-2.5 pb-2.5 pt-4 w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer',
|
||||
]"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
for="txt_display_name"
|
||||
class="absolute text-sm text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] bg-white dark:bg-gray-900 px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
|
||||
>Display Name</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- address -->
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="modalContext.data.address"
|
||||
type="text"
|
||||
id="txt_address"
|
||||
:disabled="isLoading"
|
||||
:class="[
|
||||
{ disabled: modalContext.state.isLoading },
|
||||
'block px-2.5 pb-2.5 pt-4 w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer',
|
||||
]"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
for="txt_address"
|
||||
class="absolute text-sm text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] bg-white dark:bg-gray-900 px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
|
||||
>Address</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
>
|
||||
<span v-if="!modalContext.state.isLoading">Create</span>
|
||||
<span v-else>
|
||||
<SpinnerIconVue class="text-white" />
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
</ModalDialog>
|
||||
</template>
|
||||
5
frontend/src/views/core/components/HeadingPage.vue
Normal file
5
frontend/src/views/core/components/HeadingPage.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<h2 class="font-medium leading-tight text-3xl mt-0 mb-2 text-blue-600">
|
||||
<slot></slot>
|
||||
</h2>
|
||||
</template>
|
||||
131
frontend/src/views/core/components/LoginCard.vue
Normal file
131
frontend/src/views/core/components/LoginCard.vue
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<script lang="ts" setup>
|
||||
import { useHub } from '@/hub';
|
||||
import /* * as */structs from '@thriftgen/structs_types';
|
||||
import { EyeIcon, EyeSlashIcon } from '@heroicons/vue/24/solid';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import AlertBox from '@/components/AlertBox.vue';
|
||||
import SpinnerIcon from '@/components/SpinnerIcon.vue';
|
||||
|
||||
const hub = useHub();
|
||||
const emit = defineEmits(['togglePassword', 'onInputChange', 'onLoginSuccess', 'onLoginFailed']);
|
||||
|
||||
const isPasswordShow = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const txtUsername = ref('');
|
||||
const txtPassword = ref('');
|
||||
const formData = computed(() => ({ username: txtUsername.value, password: txtPassword.value }));
|
||||
|
||||
// togglePassword to show/hide the password
|
||||
function togglePassword(ev: Event) {
|
||||
ev.preventDefault();
|
||||
isPasswordShow.value = !isPasswordShow.value;
|
||||
emit('togglePassword', isPasswordShow);
|
||||
}
|
||||
|
||||
// emit event if there's change in username and/or password
|
||||
watch(txtUsername, () => {
|
||||
emit('onInputChange');
|
||||
});
|
||||
watch(txtPassword, () => {
|
||||
emit('onInputChange');
|
||||
});
|
||||
|
||||
// form submit handler
|
||||
async function formSubmit(ev: Event) {
|
||||
ev.preventDefault();
|
||||
isLoading.value = true; // set this to true first
|
||||
try {
|
||||
const req = new structs.LoginRequest({
|
||||
...formData.value,
|
||||
});
|
||||
const res = await hub.BizCore.client.login(req);
|
||||
if (res) {
|
||||
emit('onLoginSuccess', res);
|
||||
return;
|
||||
}
|
||||
console.error('unreachable');
|
||||
} catch (e: any) {
|
||||
emit('onLoginFailed', e);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="block p-6 max-w-lg w-2/3 bg-white rounded-lg border border-gray-200 shadow-md hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
|
||||
>
|
||||
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Login</h5>
|
||||
|
||||
<form class="mt-6" @submit="formSubmit">
|
||||
<AlertBox />
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- username -->
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="txtUsername"
|
||||
type="text"
|
||||
id="txt_username"
|
||||
:disabled="isLoading"
|
||||
:class="[
|
||||
{ disabled: isLoading },
|
||||
'block px-2.5 pb-2.5 pt-4 w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer',
|
||||
]"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
for="txt_username"
|
||||
class="absolute text-sm text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] bg-white dark:bg-gray-900 px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
|
||||
>Username</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- password -->
|
||||
<div class="flex relative">
|
||||
<input
|
||||
v-model="txtPassword"
|
||||
:type="isPasswordShow ? 'text' : 'password'"
|
||||
id="txt_password"
|
||||
:disabled="isLoading"
|
||||
:class="[
|
||||
{ disabled: isLoading },
|
||||
'block px-2.5 pb-2.5 pt-4 w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer',
|
||||
]"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
for="txt_password"
|
||||
class="absolute text-sm text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] bg-white dark:bg-gray-900 px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
|
||||
>Password</label
|
||||
>
|
||||
<div class="absolute right-1.5 bottom-1.5">
|
||||
<div href="#" @click="togglePassword" class="p-2">
|
||||
<EyeIcon v-if="!isPasswordShow" class="w-5 h-5 text-sm text-gray-900" />
|
||||
<EyeSlashIcon v-else class="w-5 h-5 text-sm text-gray-900" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-baseline justify-between mt-7">
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="isLoading"
|
||||
:class="[
|
||||
{ disabled: isLoading },
|
||||
'text-white w-full bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800',
|
||||
]"
|
||||
>
|
||||
<span v-if="!isLoading">Login</span>
|
||||
<span v-else>
|
||||
<SpinnerIcon class="text-white" />
|
||||
Loading...
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- <a href="#" class="text-sm text-blue-600 hover:underline">Forgot password?</a> -->
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
139
frontend/src/views/core/components/ModalDialog.vue
Normal file
139
frontend/src/views/core/components/ModalDialog.vue
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
<!-- eslint-disable no-undef -->
|
||||
<!-- eslint-disable prefer-const -->
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, useSlots, watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
show?: boolean;
|
||||
withCloseButton?: boolean;
|
||||
slotViewName?: string;
|
||||
placement?: string;
|
||||
backdropClasses?: string;
|
||||
}>();
|
||||
const emit = defineEmits(['onHide', 'onShow', 'onToggle']);
|
||||
const slots = useSlots();
|
||||
const refTargetEl = ref();
|
||||
let modal: any;
|
||||
|
||||
function toggle() {
|
||||
modal.toggle();
|
||||
}
|
||||
|
||||
function show() {
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
modal.hide();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const targetEl = refTargetEl.value;
|
||||
|
||||
let options: any = {
|
||||
onHide: () => {
|
||||
console.log('modal hide');
|
||||
emit('onHide');
|
||||
},
|
||||
onShow: () => {
|
||||
console.log('modal show');
|
||||
emit('onShow');
|
||||
},
|
||||
onToggle: () => {
|
||||
console.log('modal toggle');
|
||||
emit('onToggle');
|
||||
},
|
||||
};
|
||||
if (props.backdropClasses) options.backdropClasses = props.backdropClasses;
|
||||
if (props.placement) options.placement = props.placement;
|
||||
|
||||
// @ts-ignore
|
||||
modal = new Modal(targetEl, options);
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(value) => {
|
||||
console.log('show changed', props.show);
|
||||
if (value) {
|
||||
show();
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const hasDefaultSlot = computed(() => slots.default);
|
||||
</script>
|
||||
<template>
|
||||
<!-- modal -->
|
||||
<div
|
||||
ref="refTargetEl"
|
||||
tabindex="-1"
|
||||
class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 md:inset-0 h-modal md:h-full"
|
||||
>
|
||||
<div class="relative p-4 w-full max-w-md h-full md:h-auto">
|
||||
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||
<button
|
||||
v-if="props.withCloseButton"
|
||||
type="button"
|
||||
class="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
data-modal-toggle="popup-modal"
|
||||
@click="hide()"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="w-5 h-5"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="sr-only">Close modal</span>
|
||||
</button>
|
||||
|
||||
<!-- <div class="p-6 text-center">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mx-auto mb-4 w-14 h-14 text-gray-400 dark:text-gray-200"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
|
||||
Are you sure you want to delete this product?
|
||||
</h3>
|
||||
<button
|
||||
data-modal-toggle="popup-modal"
|
||||
type="button"
|
||||
class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center mr-2"
|
||||
>
|
||||
Yes, I'm sure
|
||||
</button>
|
||||
<button
|
||||
data-modal-toggle="popup-modal"
|
||||
type="button"
|
||||
class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
>
|
||||
No, cancel
|
||||
</button>
|
||||
</div> -->
|
||||
|
||||
<slot :name="`view-${props.slotViewName}`" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
63
frontend/src/views/core/index.ts
Normal file
63
frontend/src/views/core/index.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { RouteRecordRaw } from 'vue-router';
|
||||
import AdminUserCreateViewVue from './AdminUserCreateView.vue';
|
||||
import AdminUserListViewVue from './AdminUserListView.vue';
|
||||
import HomeView from './HomeView.vue';
|
||||
import LoginView from './LoginView.vue';
|
||||
import UserWajibPajakListViewVue from './UserWajibPajakListView.vue';
|
||||
|
||||
|
||||
|
||||
export function routes(): RouteRecordRaw[] {
|
||||
return [
|
||||
{
|
||||
path: '/',
|
||||
name: 'core_home',
|
||||
component: HomeView,
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
navbarLink: {
|
||||
caption: 'Home',
|
||||
priority: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'core_login',
|
||||
component: LoginView,
|
||||
meta: {
|
||||
requresAuth: false,
|
||||
},
|
||||
},
|
||||
|
||||
// wajib pajak
|
||||
{
|
||||
path: '/wajib-pajak/list',
|
||||
name: 'core_wajibpajak_list',
|
||||
component: UserWajibPajakListViewVue,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
navbarLink: {
|
||||
caption: 'Wajib Pajak',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// admin
|
||||
{
|
||||
path: '/admin/users',
|
||||
name: 'core_admin_user_list',
|
||||
component: AdminUserListViewVue,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
navbarLink: {
|
||||
caption: 'Users',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export default {
|
||||
routes,
|
||||
};
|
||||
32
frontend/src/views/error/NotFoundView.vue
Normal file
32
frontend/src/views/error/NotFoundView.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
|
||||
function goBack() {
|
||||
router.back();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="grid place-items-center">
|
||||
<div class="inline-flex">
|
||||
<EmojiSadIcon class="w-10 h-10 text-gray-900 mr-3" />
|
||||
<div class="justify-center items-center text-center">
|
||||
<h1 class="text-gray-900 text-3xl leading-tight font-medium mb-2">404 Not Found</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Page or resource is not available, that's all we know.
|
||||
|
||||
<div class="mt-4">
|
||||
<button
|
||||
type="button"
|
||||
@click="goBack"
|
||||
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
|
||||
>
|
||||
Go Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
16
frontend/src/views/error/index.ts
Normal file
16
frontend/src/views/error/index.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { type RouteRecordRaw } from 'vue-router';
|
||||
import NotFoundView from './NotFoundView.vue';
|
||||
|
||||
export function routes(): RouteRecordRaw[] {
|
||||
return [
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'error_not_found',
|
||||
component: NotFoundView,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export default {
|
||||
routes,
|
||||
};
|
||||
8
frontend/src/vite-env.d.ts
vendored
Normal file
8
frontend/src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
|
||||
export default component;
|
||||
}
|
||||
32
frontend/tailwind.config.cjs
Normal file
32
frontend/tailwind.config.cjs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// import colors from 'tailwindcss/colors';
|
||||
|
||||
// import fbplugin from 'flowbite/plugin';
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{vue,js,ts,jsx,tsx}',
|
||||
'./node_modules/flowbite/**/*.js',
|
||||
'./node_modules/flowbite-vue/**/*.{js,jsx,ts,tsx}',
|
||||
'./3rdparty/flowbite-vue/**/*.{vue,js,jsx,ts,tsx}',
|
||||
],
|
||||
theme: {
|
||||
// colors: {
|
||||
// gray: colors.coolGray,
|
||||
// blue: colors.lightBlue,
|
||||
// red: colors.rose,
|
||||
// pink: colors.fuchsia,
|
||||
// },
|
||||
// fontFamily: {
|
||||
// sans: ['Graphik', 'sans-serif'],
|
||||
// serif: ['Merriweather', 'serif'],
|
||||
// },
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
// require('@acmecorp/base-tailwind-config'),
|
||||
require('flowbite/plugin'),
|
||||
],
|
||||
};
|
||||
36
frontend/tsconfig.json
Normal file
36
frontend/tsconfig.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@thriftgen/*": ["./gen/*"],
|
||||
// "@flowbite-vue": ["./3rdparty/flowbite-vue/src/*"],
|
||||
},
|
||||
// "target": "ESNext",
|
||||
// "module": "ESNext",
|
||||
"module": "ESNext",
|
||||
"target": "ES5",
|
||||
"sourceMap": true,
|
||||
"useDefineForClassFields": true,
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"allowJs": true,
|
||||
"jsx": "preserve",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitReturns": true,
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"plugins": [{ "name": "@vuedx/typescript-plugin-vue" }],
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
"env.d.ts",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
15
frontend/tsconfig.node.json
Normal file
15
frontend/tsconfig.node.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
||||
"resolveJsonModule": true,
|
||||
},
|
||||
"include": [
|
||||
"package.json",
|
||||
"vite.config.ts",
|
||||
"vite-polyfill-aliases.ts",
|
||||
]
|
||||
}
|
||||
32
frontend/vite-polyfill-aliases.ts
Normal file
32
frontend/vite-polyfill-aliases.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
export default {
|
||||
util: 'rollup-plugin-node-polyfills/polyfills/util',
|
||||
sys: 'util',
|
||||
events: 'rollup-plugin-node-polyfills/polyfills/events',
|
||||
stream: 'rollup-plugin-node-polyfills/polyfills/stream',
|
||||
path: 'rollup-plugin-node-polyfills/polyfills/path',
|
||||
querystring: 'rollup-plugin-node-polyfills/polyfills/qs',
|
||||
punycode: 'rollup-plugin-node-polyfills/polyfills/punycode',
|
||||
url: 'rollup-plugin-node-polyfills/polyfills/url',
|
||||
string_decoder: 'rollup-plugin-node-polyfills/polyfills/string-decoder',
|
||||
http: 'rollup-plugin-node-polyfills/polyfills/http',
|
||||
https: 'rollup-plugin-node-polyfills/polyfills/http',
|
||||
os: 'rollup-plugin-node-polyfills/polyfills/os',
|
||||
assert: 'rollup-plugin-node-polyfills/polyfills/assert',
|
||||
constants: 'rollup-plugin-node-polyfills/polyfills/constants',
|
||||
_stream_duplex: 'rollup-plugin-node-polyfills/polyfills/readable-stream/duplex',
|
||||
_stream_passthrough: 'rollup-plugin-node-polyfills/polyfills/readable-stream/passthrough',
|
||||
_stream_readable: 'rollup-plugin-node-polyfills/polyfills/readable-stream/readable',
|
||||
_stream_writable: 'rollup-plugin-node-polyfills/polyfills/readable-stream/writable',
|
||||
_stream_transform: 'rollup-plugin-node-polyfills/polyfills/readable-stream/transform',
|
||||
timers: 'rollup-plugin-node-polyfills/polyfills/timers',
|
||||
console: 'rollup-plugin-node-polyfills/polyfills/console',
|
||||
vm: 'rollup-plugin-node-polyfills/polyfills/vm',
|
||||
zlib: 'rollup-plugin-node-polyfills/polyfills/zlib',
|
||||
tty: 'rollup-plugin-node-polyfills/polyfills/tty',
|
||||
domain: 'rollup-plugin-node-polyfills/polyfills/domain',
|
||||
buffer: 'rollup-plugin-node-polyfills/polyfills/buffer-es6',
|
||||
process: 'rollup-plugin-node-polyfills/polyfills/process-es6',
|
||||
|
||||
// Buffer: 'buffer/',
|
||||
// process: 'rollup-plugin-node-globals',
|
||||
};
|
||||
349
frontend/vite.config.ts
Normal file
349
frontend/vite.config.ts
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
import { fileURLToPath, URL } from 'node:url';
|
||||
import { defineConfig, optimizeDeps } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import { esbuildCommonjs, viteCommonjs } from '@originjs/vite-plugin-commonjs';
|
||||
|
||||
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
|
||||
import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill';
|
||||
|
||||
import { Plugin } from 'rollup';
|
||||
|
||||
import { splitVendorChunkPlugin } from 'vite';
|
||||
import rollupNodePolyfills from 'rollup-plugin-node-polyfills';
|
||||
import viteNodePolyfills from 'vite-plugin-node-stdlib-browser';
|
||||
import { nodeResolve as rollupNodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import rollupCommonjs from '@rollup/plugin-commonjs';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import compiler from '@ampproject/rollup-plugin-closure-compiler';
|
||||
import { babel, getBabelOutputPlugin } from '@rollup/plugin-babel';
|
||||
import legacy from '@vitejs/plugin-legacy';
|
||||
import uglify from '@lopatnov/rollup-plugin-uglify';
|
||||
import { obfuscator } from 'rollup-obfuscator';
|
||||
import { chunkSplitPlugin } from 'vite-plugin-chunk-split';
|
||||
|
||||
import { dependencies } from './package.json';
|
||||
import polyfillAliases from './vite-polyfill-aliases';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export function injectSecPlugin(mode) {
|
||||
return ['production', 'live'].indexOf(mode) !== -1 ? secPlugins : [];
|
||||
}
|
||||
export const secPlugins: Plugin[] = [
|
||||
// NOT WORK.
|
||||
// babel({
|
||||
// babelHelpers: 'bundled',
|
||||
// presets: [[
|
||||
// "@babel/preset-env",
|
||||
// {
|
||||
// "corejs": "3.22",
|
||||
// "useBuiltIns": "entry",//"usage",
|
||||
// "targets": {
|
||||
// "ie": "11"
|
||||
// }
|
||||
// }
|
||||
// ]],
|
||||
// }),
|
||||
|
||||
// terser({
|
||||
// parse: {},
|
||||
// // compress: {
|
||||
// // dead_code: true,
|
||||
// // keep_fnames: false,
|
||||
// // },
|
||||
|
||||
// // mangle: {
|
||||
// // keep_fnames: false,
|
||||
// // properties: {
|
||||
// // builtins: false,
|
||||
// // // keep_quoted: false,
|
||||
// // // nth_identifier: ,
|
||||
// // // reserved: [
|
||||
// // // '_mergeNamespaces',
|
||||
// // // 'polyfill',
|
||||
// // // 'class', 'alt', 'src', 'href', 'target', "value", "type", "onClick"
|
||||
// // // ],
|
||||
// // // regex: /^(T*Protocol|T*Transport|T*Error|transport|*Exception|write*|read*|skip|*Zigzag|zigzag*|VERSION*|TYPE*|COMPACT*|HEADER*|FLAG*|TINFO*|MAX*|Varint*|varint*|CT_*|*TType|flush)$/,
|
||||
// // // regex: /(thrift|Thrift|Protocol|Transport|Error|transport|Exception|write|read|skip|zigzag|VERSION|TYPE|COMPACT|HEADER|FLAG|TINFO|MAX|Varint|varint|CT|TType|flush)/,
|
||||
// // },
|
||||
// // },
|
||||
|
||||
// mangle: {
|
||||
// keep_classnames: true,
|
||||
// keep_fnames: true,
|
||||
// properties: {
|
||||
// keep_quoted: true,
|
||||
// // builtins: false,
|
||||
// // ||SET|LIST|||I64|
|
||||
// // regex: /(^_|^is|eject|esolve|set|next|query|create|STRING|UTF7|UTF8|UTF16|DOUBLE|Error|error|dir|MAP|STOP|VOID|thrift|PROTOCOL|METHOD|method|UNKNOWN|TRANSFORM|VALID|SEQUENCE|RESULT|ERROR|CALL|REPLY|stack|field|Field|ONEWAY|EXCEPTION|client|Client|Type|socket|watch|mount|install|comp|emit|prop|destroy|Recv|Send|service|Service|Thrift|Protocol|Transport|transport|protocol|output|Error|transport|Exception|write|read|skip|zigzag|VERSION|TYPE|COMPACT|HEADER|FLAG|TINFO|MAX|Varint|varint|CT|TType|trans|call|url|xhr|XHR|header|log|before|after|update|insert|console|read|write|flush)/,
|
||||
// regex: new RegExp(
|
||||
// [
|
||||
// // var prefixed with underscore
|
||||
// '^_',
|
||||
|
||||
// // thrift words
|
||||
// ...(
|
||||
// '' +
|
||||
// // 'Thrift|thrift|Field|^field|Protocol|Transport|Connection|Client|Protocol|Service|Type|Error|Exception|' +
|
||||
// // // 'Recv|recv|Send|send|'+
|
||||
// // 'write|read|Socket|socket|trans|prot|rotoco|tack|ervice|Trans|' +
|
||||
// // 'CALL|REPLY|ONEWAY|VALID|^UNK|BAD_|IMPL|_LIMIT|SIZE_|EXCEPTION|RESULT|ERROR|TRANSFORM|PROTOCOL|VERSION|TYPE|CT|TType|METHOD|' +
|
||||
// // '^STOP|^VOID|^STRING|^LIST|^MAP|^SET|^UTF7|^UTF8|^UTF16|^DOUBLE|^I64|^I32|^I16|^BOOL|^BYTE|^I08|' +
|
||||
// // 'headers|igzag|^xhr|^XHR|^flush' +
|
||||
// ''
|
||||
// )
|
||||
// .split('|')
|
||||
// .map((x) => x.trim())
|
||||
// .filter((x) => x != ''),
|
||||
// ].join('|'),
|
||||
// '',
|
||||
// ),
|
||||
// },
|
||||
// // eval: true,
|
||||
// // module: true,
|
||||
// // toplevel: true,
|
||||
// // safari10: true,
|
||||
|
||||
// // properties: false,
|
||||
// },
|
||||
// format: {
|
||||
// comments: 'some',
|
||||
// },
|
||||
// }),
|
||||
|
||||
// compiler({
|
||||
// // https://github.com/google/closure-compiler/wiki/Flags-and-Options
|
||||
// // https://github.com/google/closure-compiler/blob/5707cfe4fa3be9cfe9b2f713a47f0080b08c57cb/src/com/google/javascript/jscomp/parsing/ParserRunner.java#L176
|
||||
// // compilation_level: 'ADVANCED',
|
||||
// // warning_level: 'VERBOSE',
|
||||
// // language_in: 'ECMASCRIPT_NEXT',
|
||||
// // language_in: 'STABLE',
|
||||
|
||||
// // current workaround, modify renderChunk preCompileOutput.
|
||||
// // ConstTransform pre
|
||||
// // LiteralComputedKeys post
|
||||
// language_in: 'unstable',
|
||||
// // language_out: 'ECMASCRIPT_NEXT',
|
||||
// jscomp_off: ['undefinedVars'],
|
||||
// }),
|
||||
|
||||
obfuscator({
|
||||
compact: true,
|
||||
forceTransformStrings: Array.from(
|
||||
new Set([
|
||||
'thrift',
|
||||
'Thrift',
|
||||
'vite',
|
||||
'Vite',
|
||||
'vue',
|
||||
'Vue',
|
||||
...Object.keys(dependencies)
|
||||
.map((x) => x.replace('@', '-').replace('/', '-'))
|
||||
.map((x) => x.split('-'))
|
||||
.flat()
|
||||
.filter((x) => x != ''),
|
||||
]),
|
||||
),
|
||||
splitStrings: true,
|
||||
stringArray: true,
|
||||
// stringArrayEncoding: ['base64', 'none'], // 'rc4',
|
||||
stringArrayThreshold: 0.8,
|
||||
// stringArrayIndexShift: true,
|
||||
// stringArrayWrappersChainedCalls: true,
|
||||
// stringArrayRotate: true,
|
||||
|
||||
// stringArrayCallsTransform: true,
|
||||
selfDefending: false,
|
||||
|
||||
// controlFlowFlattening: true,
|
||||
// controlFlowFlatteningThreshold: 0.8,
|
||||
// transformObjectKeys: true,
|
||||
|
||||
renameGlobals: false,
|
||||
|
||||
// BROKEN!
|
||||
// renameProperties: true,
|
||||
// renamePropertiesMode: 'safe',
|
||||
}),
|
||||
|
||||
terser({
|
||||
compress: {
|
||||
reduce_funcs: true,
|
||||
reduce_vars: true,
|
||||
},
|
||||
mangle: {
|
||||
properties: {
|
||||
// builtins: false,
|
||||
keep_quoted: true,
|
||||
regex: /^_/, // obfuscator underscore variable
|
||||
},
|
||||
eval: true,
|
||||
module: true,
|
||||
toplevel: true,
|
||||
safari10: true,
|
||||
},
|
||||
format: {
|
||||
comments: 'some',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
|
||||
export default defineConfig(({ command, mode, ssrBuild }) => {
|
||||
return {
|
||||
plugins: [viteCommonjs(), vue(), vueJsx()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@thriftgen': fileURLToPath(new URL('./gen', import.meta.url)),
|
||||
|
||||
...polyfillAliases,
|
||||
},
|
||||
},
|
||||
define: {
|
||||
global: 'window',
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
define: { global: 'globalThis' },
|
||||
plugins: [
|
||||
NodeGlobalsPolyfillPlugin({
|
||||
process: true,
|
||||
buffer: true,
|
||||
}),
|
||||
NodeModulesPolyfillPlugin(),
|
||||
],
|
||||
},
|
||||
// exclude: ['flowbite', 'flowbite-vue', 'thrift'],
|
||||
},
|
||||
build: {
|
||||
outDir: '../public',
|
||||
manifest: true,
|
||||
minify: false,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: resolve(__dirname, 'index.html'),
|
||||
},
|
||||
output: {
|
||||
// to be able to use manual chunk - iife
|
||||
// inlineDynamicImports: true,
|
||||
manualChunks(id, { getModuleInfo, getModuleIds }) {
|
||||
// const dynImpIds = getModuleInfo(id).dynamicImporters;
|
||||
|
||||
// if (/(gen\/|thrift|Thrift|util|events|inherits|WebSocket|Int64|ws)/.test(id)) return 'ext';
|
||||
// if (/flowbite/.test(id)) return 'flowbite';
|
||||
// if (/data-table/.test(id)) return 'edt';
|
||||
// if (/scrollbar/.test(id)) return 'scrollbar';
|
||||
// if (/(node_modules)/.test(id)) return 'vendor';
|
||||
|
||||
// if (id in dependencies) {
|
||||
// return 'vendor';
|
||||
// }
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
rollupNodePolyfills(),
|
||||
rollupNodeResolve(),
|
||||
rollupCommonjs({}),
|
||||
esbuildCommonjs(['thrift']),
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const x = defineConfig(({ command, mode, ssrBuild }) => {
|
||||
return {
|
||||
plugins: [viteNodePolyfills(), viteCommonjs(), vue(), vueJsx()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@thriftgen': fileURLToPath(new URL('./gen', import.meta.url)),
|
||||
// '@flowbite-vue': fileURLToPath(new URL('./3rdparty/flowbite-vue/src', import.meta.url)),
|
||||
|
||||
...polyfillAliases,
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
define: { global: 'globalThis' },
|
||||
plugins: [
|
||||
NodeGlobalsPolyfillPlugin({
|
||||
process: true,
|
||||
buffer: true,
|
||||
}),
|
||||
NodeModulesPolyfillPlugin(),
|
||||
],
|
||||
},
|
||||
// include: [],
|
||||
// exclude: [/@thriftgen/],
|
||||
// exclude: [
|
||||
// 'flowbite',
|
||||
// 'flowbite-vue',
|
||||
// 'thrift',
|
||||
// 'vue3-easy-data-table',
|
||||
// 'vue3-perfect-scrollbar',
|
||||
// ...['structs_types', 'exceptions_types', 'CoreService'].map((x) => '@thriftgen/' + x),
|
||||
// ],
|
||||
},
|
||||
build: {
|
||||
outDir: '../public',
|
||||
manifest: true,
|
||||
// minify: 'terser',
|
||||
minify: false,
|
||||
|
||||
// target: 'esnext',
|
||||
|
||||
rollupOptions: {
|
||||
// input: {
|
||||
// main: resolve(__dirname, 'index.html'),
|
||||
// },
|
||||
// output: {
|
||||
// // format: 'iife',
|
||||
// // format: 'cjs',
|
||||
// globals: {
|
||||
// // vue: "Vue"
|
||||
// },
|
||||
|
||||
// // to be able to use manual chunk - iife
|
||||
// // inlineDynamicImports: true,
|
||||
// // manualChunks(id, { getModuleInfo, getModuleIds }) {
|
||||
// // // if (/(gen\/|thrift|Thrift|util|inherits|WebSocket|Int64)/.test(id)) return 'ext';
|
||||
// // // const dynImpIds = getModuleInfo(id).dynamicImporters;
|
||||
// // if (/data-table/.test(id)) return 'edt';
|
||||
// // if (/(node_modules)/.test(id)) return 'vendor';
|
||||
// // // if (id in dependencies) {
|
||||
// // // return 'vendor';
|
||||
// // // }
|
||||
// // },
|
||||
// },
|
||||
external: [
|
||||
// "vue",
|
||||
],
|
||||
plugins: [
|
||||
// chunkSplitPlugin({
|
||||
// strategy: 'single-vendor',
|
||||
// customSplitting: {
|
||||
// 'thrift-vendor': ['thrift'],
|
||||
// 'flowbite-vendor': ['flowbite'],
|
||||
// },
|
||||
// }),
|
||||
|
||||
// splitVendorChunkPlugin(),
|
||||
rollupNodePolyfills(),
|
||||
rollupNodeResolve(),
|
||||
rollupCommonjs({}),
|
||||
esbuildCommonjs(['thrift']),
|
||||
|
||||
// NOT WORK.
|
||||
// ...legacy({
|
||||
// targets: ['defaults', 'IE 11'],
|
||||
// }),
|
||||
|
||||
...injectSecPlugin(mode),
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
166
frontend/vite.config.ts.timestamp-1668343269206.mjs
Normal file
166
frontend/vite.config.ts.timestamp-1668343269206.mjs
Normal file
File diff suppressed because one or more lines are too long
45
go.mod
Normal file
45
go.mod
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
module wpw-common
|
||||
|
||||
go 1.19
|
||||
|
||||
replace github.com/cloudwego/thriftgo => github.com/ii64/thriftgo v0.2.1-2
|
||||
|
||||
require (
|
||||
github.com/apache/thrift v0.17.0
|
||||
github.com/cloudwego/thriftgo v0.0.0-00010101000000-000000000000
|
||||
github.com/go-playground/locales v0.14.0
|
||||
github.com/go-playground/universal-translator v0.18.0
|
||||
github.com/go-playground/validator/v10 v10.11.1
|
||||
github.com/gofiber/fiber/v2 v2.39.0
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/ii64/thrift-idl-builder v0.1.4
|
||||
github.com/joho/godotenv v1.4.0
|
||||
go.uber.org/zap v1.23.0
|
||||
golang.org/x/crypto v0.1.0
|
||||
gorm.io/driver/mysql v1.4.3
|
||||
gorm.io/driver/sqlite v1.4.3
|
||||
gorm.io/gorm v1.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/stretchr/testify v1.8.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.41.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
)
|
||||
120
go.sum
Normal file
120
go.sum
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/apache/thrift v0.17.0 h1:cMd2aj52n+8VoAtvSvLn4kDC3aZ6IAkBuqWQ2IDu7wo=
|
||||
github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gofiber/fiber/v2 v2.39.0 h1:uhWpYQ6EHN8J7FOPYbI2hrdBD/KNZBC5CjbuOd4QUt4=
|
||||
github.com/gofiber/fiber/v2 v2.39.0/go.mod h1:Cmuu+elPYGqlvQvdKyjtYsjGMi69PDp8a1AY2I5B2gM=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/ii64/thrift-idl-builder v0.1.4 h1:hM+9QMCyDrKCUncUxq3gNvbNuQS107GoaVV0XZ+e27E=
|
||||
github.com/ii64/thrift-idl-builder v0.1.4/go.mod h1:smCsPHGI0sQNUR8YIpg2RzzlSugWjNoY6pyLdmvEoKw=
|
||||
github.com/ii64/thriftgo v0.2.1-2 h1:bYWxBeyu5EA0yHfevJL+BUGlvdyChvynG48k8zatlQ0=
|
||||
github.com/ii64/thriftgo v0.2.1-2/go.mod h1:8i9AF5uDdWHGqzUhXDlubCjx4MEfKvWXGQlMWyR0tM4=
|
||||
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.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
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.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY=
|
||||
github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k=
|
||||
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
|
||||
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
|
||||
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74=
|
||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
7
idl/biz/core/exceptions.thrift
Normal file
7
idl/biz/core/exceptions.thrift
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace go biz.core.exceptions
|
||||
|
||||
exception CoreServicesException {
|
||||
1: i32 code
|
||||
2: string message
|
||||
3: map<string, string> parameters
|
||||
}
|
||||
71
idl/biz/core/service.thrift
Normal file
71
idl/biz/core/service.thrift
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
include "./structs.thrift"
|
||||
include "./exceptions.thrift"
|
||||
|
||||
namespace go biz.core
|
||||
|
||||
service CoreService {
|
||||
// oneway void asd()
|
||||
|
||||
structs.LoginResponse login(
|
||||
1: structs.LoginRequest request
|
||||
) throws (1: exceptions.CoreServicesException e)
|
||||
|
||||
//@authenticated
|
||||
structs.GetProfileResponse getProfile(
|
||||
1: structs.GetProfileRequest request
|
||||
) throws (1: exceptions.CoreServicesException e)
|
||||
|
||||
//@authenticated
|
||||
structs.LogoutResponse logout(
|
||||
1: structs.LogoutRequest request
|
||||
) throws (1: exceptions.CoreServicesException e)
|
||||
|
||||
|
||||
|
||||
//@authenticated
|
||||
//@role(Admin)
|
||||
// Get list of users registered in system
|
||||
structs.GetUserListResponse getUserList(
|
||||
1: structs.GetUserListRequest request
|
||||
) throws (1: exceptions.CoreServicesException e)
|
||||
|
||||
//@authenticated
|
||||
//@role(Admin)
|
||||
structs.CreateUserResponse createUser(
|
||||
1: structs.CreateUserRequest request
|
||||
) throws (1: exceptions.CoreServicesException e)
|
||||
|
||||
//@authenticated
|
||||
//@role(Admin)
|
||||
structs.DeleteUsersResponse deleteUsers(
|
||||
1: structs.DeleteUsersRequest request,
|
||||
) throws (1: exceptions.CoreServicesException e)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//@authenticated
|
||||
//@role(All)
|
||||
// Get list of accessible WajibPajak by OWNED|ROLE
|
||||
structs.GetWajibPajakListResponse getWajibPajakList(
|
||||
1: structs.GetWajibPajakListRequest request
|
||||
) throws (1: exceptions.CoreServicesException e)
|
||||
|
||||
//@authenticated
|
||||
//@role(All)
|
||||
// Create WajibPajak
|
||||
structs.CreateWajibPajakResponse createWajibPajak(
|
||||
1: structs.CreateWajibPajakRequest request
|
||||
) throws (1: exceptions.CoreServicesException e)
|
||||
|
||||
//@authenticated
|
||||
//@role(All)
|
||||
structs.DeleteWajibpajakListResponse deleteWajibPajakList(
|
||||
1: structs.DeleteWajibpajakListRequest request
|
||||
) throws (1: exceptions.CoreServicesException e)
|
||||
|
||||
|
||||
|
||||
}
|
||||
200
idl/biz/core/structs.thrift
Normal file
200
idl/biz/core/structs.thrift
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
namespace go biz.core.structs
|
||||
|
||||
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
// Please don't mix DTO and DAO !
|
||||
// This design is intended to be as-is.
|
||||
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
|
||||
enum RoleType {
|
||||
UNKNOWN = 0
|
||||
SYSTEM = 1
|
||||
USER = 2
|
||||
|
||||
WP_OWNER = 100
|
||||
WP_ADMIN = 110
|
||||
}
|
||||
|
||||
struct Role {
|
||||
1: i64 id (go.tag = "gorm:\"primaryKey\"")
|
||||
|
||||
5: string displayName (go.tag = "core_v1_dto:\"required\"")
|
||||
6: RoleType roleType (go.tag = "core_v1_dto:\"required\"")
|
||||
|
||||
// user that create this role
|
||||
39: optional User creator (go.tag = "gorm:\"foreignKey:id;references:id;\"")
|
||||
// users with this role
|
||||
40: optional list<User> users (go.tag = "gorm:\"many2many:user_roles;foreignKey:id;references:id;\"")
|
||||
// wajib pajak bound with this role
|
||||
41: optional list<WajibPajak> wajibPajakList (go.tag = "gorm:\"many2many:wp_roles;foreignKey:id;references:id\"")
|
||||
}
|
||||
|
||||
//@direct
|
||||
struct User {
|
||||
1: i64 id (go.tag = "gorm:\"primaryKey\"")
|
||||
2: string username (go.tag = "core_v1_dto:\"required\" gorm:\"index\"")
|
||||
3: string password (go.tag = "core_v1_dto:\"required\"")
|
||||
4: string displayName (go.tag = "core_v1_dto:\"required\"")
|
||||
|
||||
60: binary privateKey
|
||||
61: binary publicKey
|
||||
|
||||
// system roles
|
||||
40: optional list<Role> roles (go.tag = "gorm:\"many2many:user_roles;foreignKey:id;references:id;\"")
|
||||
// wp owned by user
|
||||
45: optional list<WajibPajak> ownedWajibPajakList (go.tag = "gorm:\"many2many:user_wps;foreignKey:id;references:id;constraint:OnDelete:CASCADE\"")
|
||||
// wp roles roles owned by user
|
||||
50: optional list<Role> rolesWajibPajakList (go.tag = "gorm:\"many2many:wp_roles;foreignKey:id;references:id;\"")
|
||||
|
||||
}
|
||||
|
||||
|
||||
//@direct
|
||||
// struct JenisPajakXX {
|
||||
// 1: i64 id (go.tag = "gorm:\"primaryKey\"")
|
||||
// 2: string displayName (go.tag = "core_v1_dto:\"required\"")
|
||||
// }
|
||||
|
||||
enum JenisPajak {
|
||||
UNKNOWN = 0
|
||||
PPH_21 = 1
|
||||
PPH_23 = 2
|
||||
PPH_25 = 3
|
||||
PPH_26 = 4
|
||||
PPH_4_2 = 5
|
||||
PPH_15 = 6
|
||||
PPN = 7
|
||||
TAHUNAN = 8
|
||||
}
|
||||
|
||||
|
||||
struct WajibPajakProfile {
|
||||
// 1: i64 id (go.tag = "gorm:\"primaryKey\"")
|
||||
2: string npwp (go.tag = "core_v1_dto:\"required\"")
|
||||
3: string displayName (go.tag = "core_v1_dto:\"required\"")
|
||||
4: string address (go.tag = "core_v1_dto:\"required\"")
|
||||
}
|
||||
|
||||
struct WajibPajakTaxObligation {
|
||||
1: i64 id (go.tag = "gorm:\"primaryKey\"")
|
||||
2: JenisPajak obligation (go.tag = "gorm:\"foreignKey:id;references:id\"")
|
||||
3: bool isActive (go.tag = "")
|
||||
}
|
||||
|
||||
//@direct
|
||||
struct WajibPajak {
|
||||
1: i64 id (go.tag = "gorm:\"primaryKey\"")
|
||||
2: WajibPajakProfile profile (go.tag = "gorm:\"embedded;embeddedPrefix:profile_;constraint:OnDelete:CASCADE\" core_v1_dto:\"required\"")
|
||||
|
||||
// wajib pajak owner
|
||||
30: list<User> owners (go.tag = "gorm:\"many2many:wp_roles;foreignKey:id;references:id;\"")
|
||||
|
||||
// wajib pajak tax obligations
|
||||
40: optional list<WajibPajakTaxObligation> taxObligations (go.tag = "gorm:\"many2many:wp_obligations;foreignKey:id;references:id;\"")
|
||||
// role bound to this WajibPajak
|
||||
45: optional list<Role> roles (go.tag = "gorm:\"many2many:wp_roles;foreignKey:id;references:id;constraint:OnDelete:CASCADE\"")
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
struct Pagination {
|
||||
1: i64 page
|
||||
2: i64 rowsPerPage
|
||||
}
|
||||
|
||||
struct AlertInfo {
|
||||
1: string title
|
||||
2: string description
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
struct LoginRequest {
|
||||
1: string username (go.tag = "core_v1_dto:\"required\"")
|
||||
2: string password (go.tag = "core_v1_dto:\"required\"")
|
||||
}
|
||||
struct LoginResponse {
|
||||
1: string trkToken
|
||||
2: string token
|
||||
10: optional User profile
|
||||
}
|
||||
|
||||
struct LogoutRequest {
|
||||
1: string token (go.tag = "core_v1_dto:\"required\"")
|
||||
}
|
||||
struct LogoutResponse {
|
||||
}
|
||||
|
||||
struct GetProfileRequest {
|
||||
}
|
||||
struct GetProfileResponse {
|
||||
2: User profile
|
||||
}
|
||||
|
||||
enum WajibPajakOwnership {
|
||||
UNKNOWN = 0
|
||||
OWNED = 1
|
||||
ROLE = 2
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct GetUserListRequest {
|
||||
1: Pagination pagination
|
||||
3: string searchTerm
|
||||
}
|
||||
struct GetUserListResponse {
|
||||
1: Pagination pagination
|
||||
2: i64 totalUsers
|
||||
3: list<User> users
|
||||
}
|
||||
|
||||
struct CreateUserRequest {
|
||||
1: User user
|
||||
}
|
||||
struct CreateUserResponse {
|
||||
}
|
||||
|
||||
struct DeleteUsersRequest {
|
||||
1: list<i64> userIds
|
||||
}
|
||||
struct DeleteUsersResponse {
|
||||
1: list<i64> success
|
||||
2: list<i64> ignored
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct GetWajibPajakListRequest {
|
||||
1: Pagination pagination
|
||||
2: WajibPajakOwnership ownership
|
||||
3: string searchTerm
|
||||
}
|
||||
struct GetWajibPajakListResponse {
|
||||
1: Pagination pagination
|
||||
2: i64 totalWajibPajak
|
||||
3: list<WajibPajak> wajibPajakList
|
||||
}
|
||||
|
||||
|
||||
struct CreateWajibPajakRequest {
|
||||
1: WajibPajak wajibPajak (go.tag = "core_v1_dto:\"required\"")
|
||||
}
|
||||
struct CreateWajibPajakResponse {
|
||||
}
|
||||
|
||||
|
||||
struct DeleteWajibpajakListRequest {
|
||||
1: list<i64> wpIds
|
||||
}
|
||||
struct DeleteWajibpajakListResponse {
|
||||
1: list<i64> success
|
||||
2: list<i64> ignored
|
||||
}
|
||||
7
idl/common/exceptionsc.thrift
Normal file
7
idl/common/exceptionsc.thrift
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace go common.exceptionsc
|
||||
|
||||
exception CommonException {
|
||||
1: i32 code
|
||||
2: string message
|
||||
3: optional map<string, string> metadata
|
||||
}
|
||||
3
idl/common/service.thrift
Normal file
3
idl/common/service.thrift
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
include "./exceptionsc.thrift"
|
||||
|
||||
namespace go common
|
||||
383
internal/biz/core/core.go
Normal file
383
internal/biz/core/core.go
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
package coresvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"wpw-common/internal"
|
||||
"wpw-common/internal/http"
|
||||
|
||||
"wpw-common/internal/vdext"
|
||||
"wpw-common/pkg/gen/biz/core"
|
||||
"wpw-common/pkg/gen/biz/core/exceptions"
|
||||
"wpw-common/pkg/gen/biz/core/structs"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2/middleware/session"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type scopeFunc = func(db *gorm.DB) *gorm.DB
|
||||
|
||||
type CoreService struct {
|
||||
vd *validator.Validate
|
||||
logger *zap.SugaredLogger
|
||||
db *gorm.DB
|
||||
|
||||
secret []byte
|
||||
authExpireDur time.Duration
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register(&structs.User{})
|
||||
}
|
||||
|
||||
var _ core.CoreService = (*CoreService)(nil)
|
||||
|
||||
type CoreV1ServiceParams struct {
|
||||
Logger *zap.Logger
|
||||
DB *gorm.DB
|
||||
Secret []byte
|
||||
}
|
||||
|
||||
func NewCoreServiceFromAppContext(appCtx *internal.AppContext) *CoreService {
|
||||
var (
|
||||
argName string
|
||||
db, secret any
|
||||
|
||||
exist bool
|
||||
)
|
||||
// db
|
||||
db, exist = appCtx.Data["db"]
|
||||
if !exist {
|
||||
argName = "db"
|
||||
goto invalid_arguments
|
||||
}
|
||||
// secret
|
||||
secret, exist = appCtx.Data["secret"]
|
||||
if !exist {
|
||||
argName = "secret"
|
||||
goto invalid_arguments
|
||||
}
|
||||
return NewCoreService(CoreV1ServiceParams{
|
||||
Logger: appCtx.Logger,
|
||||
DB: db.(*gorm.DB),
|
||||
Secret: []byte(secret.(string)),
|
||||
})
|
||||
|
||||
invalid_arguments:
|
||||
appCtx.Logger.Sugar().Fatal("core v1 service requires `%s` on application context", argName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewCoreService(params CoreV1ServiceParams) *CoreService {
|
||||
c := &CoreService{
|
||||
vd: vdext.New("core_v1_dto"),
|
||||
logger: params.Logger.Sugar().Named("core-v1"),
|
||||
db: params.DB,
|
||||
secret: params.Secret,
|
||||
authExpireDur: time.Hour * 5,
|
||||
}
|
||||
|
||||
token := jwt.New(jwt.SigningMethodEdDSA)
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
_ = claims
|
||||
|
||||
c.migrateSchemeAndData()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CoreService) getEd25519KeyPair() (pri ed25519.PrivateKey, pub ed25519.PublicKey, err error) {
|
||||
pub, pri, err = ed25519.GenerateKey(rand.Reader)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) generateJwtTokenForUser(user *structs.User, jti string) (tokenStr string, err error) {
|
||||
// user.PrivateKey
|
||||
token := jwt.New(jwt.SigningMethodEdDSA)
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
claims["exp"] = time.Now().Add(c.authExpireDur)
|
||||
claims["iss"] = "core-v1"
|
||||
claims["uid"] = user.ID
|
||||
claims["jti"] = jti
|
||||
claims["username"] = user.Username
|
||||
var roleIds []int64
|
||||
for _, role := range user.Roles {
|
||||
roleIds = append(roleIds, role.ID)
|
||||
}
|
||||
claims["roles"] = roleIds
|
||||
|
||||
tokenStr, err = token.SignedString(ed25519.PrivateKey(user.PrivateKey))
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (c *CoreService) stripConfidentialUserData(u *structs.User) {
|
||||
u.Password = ""
|
||||
u.PrivateKey = nil
|
||||
u.PublicKey = nil
|
||||
}
|
||||
|
||||
func (c *CoreService) getSession(ctx context.Context) (sess *session.Session, err error) {
|
||||
fctx := http.GetFiberFromContext(ctx)
|
||||
sess, err = http.Session.Get(fctx)
|
||||
if err != nil {
|
||||
c.logger.Error("get session failed: ", err)
|
||||
exc := exceptions.NewCoreServicesException()
|
||||
exc.Code = 500
|
||||
exc.Message = "unable to get session"
|
||||
exc.Parameters = map[string]string{
|
||||
"inner": err.Error(),
|
||||
}
|
||||
err = exc
|
||||
return
|
||||
}
|
||||
if authToken := fctx.Request().Header.Peek("X-Hub-Auth-Token"); authToken != nil {
|
||||
// tbd
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
ErrBadRequest = errors.New("bad request")
|
||||
ErrForbidden = errors.New("forbidden")
|
||||
ErrNotAuthenticated = errors.New("not authenticated")
|
||||
ErrInvalidState = errors.New("invalid state")
|
||||
)
|
||||
|
||||
func (c *CoreService) checkSession(sess *session.Session) (user *structs.User, err error) {
|
||||
userVal := sess.Get("user")
|
||||
if userVal == nil {
|
||||
err = ErrNotAuthenticated
|
||||
return
|
||||
}
|
||||
var ok bool
|
||||
user, ok = userVal.(*structs.User)
|
||||
if !ok {
|
||||
err = ErrNotAuthenticated
|
||||
return
|
||||
}
|
||||
tx := c.db.Where("id = ?", user.ID).First(user)
|
||||
if err = tx.Error; err != nil {
|
||||
// must check if the user is still valid / no db error
|
||||
// or we just revoke the session.
|
||||
if err = sess.Destroy(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: also use the JWT ?
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) getAndCheckSession(ctx context.Context) (user *structs.User, err error) {
|
||||
var sess *session.Session
|
||||
if sess, err = c.getSession(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if user, err = c.checkSession(sess); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) getAndCheckSessionAdmin(ctx context.Context) (user *structs.User, err error) {
|
||||
if user, err = c.getAndCheckSession(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
// user with type system
|
||||
if !c.isSystemUser(user) {
|
||||
err = ErrForbidden
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) isSystemUser(user *structs.User) bool {
|
||||
for _, role := range user.Roles {
|
||||
if role.RoleType == structs.RoleType_SYSTEM {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *CoreService) alert2Str(alert *structs.AlertInfo) (s string) {
|
||||
var b []byte
|
||||
var err error
|
||||
if b, err = json.Marshal(alert); err != nil {
|
||||
s = "{}"
|
||||
return
|
||||
}
|
||||
s = string(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) wrapServiceError(err error, customMessage ...string) error {
|
||||
if errVal, ok := err.(*exceptions.CoreServicesException); ok {
|
||||
return errVal
|
||||
}
|
||||
exc := exceptions.NewCoreServicesException()
|
||||
exc.Message = err.Error()
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
exc.Code = 404
|
||||
alert := &structs.AlertInfo{
|
||||
Title: "Resource",
|
||||
Description: "Resource not found",
|
||||
}
|
||||
if len(customMessage) >= 2 {
|
||||
alert.Title = customMessage[0]
|
||||
alert.Description = customMessage[1]
|
||||
} else if len(customMessage) >= 1 {
|
||||
alert.Description = customMessage[0]
|
||||
}
|
||||
exc.Parameters = map[string]string{
|
||||
"alert": c.alert2Str(alert),
|
||||
}
|
||||
case ErrForbidden:
|
||||
exc.Code = 403
|
||||
exc.Parameters = map[string]string{
|
||||
"alert": c.alert2Str(&structs.AlertInfo{
|
||||
Title: "Access Error",
|
||||
Description: err.Error(),
|
||||
}),
|
||||
}
|
||||
case ErrBadRequest:
|
||||
exc.Code = 400
|
||||
exc.Parameters = map[string]string{
|
||||
"alert": c.alert2Str(&structs.AlertInfo{
|
||||
Title: "Input Error",
|
||||
Description: err.Error(),
|
||||
}),
|
||||
}
|
||||
case ErrNotAuthenticated:
|
||||
exc.Code = 401
|
||||
exc.Parameters = map[string]string{
|
||||
"alert": c.alert2Str(&structs.AlertInfo{
|
||||
Title: "Auth Error",
|
||||
Description: err.Error(),
|
||||
}),
|
||||
}
|
||||
case ErrInvalidState:
|
||||
exc.Code = 500
|
||||
exc.Parameters = map[string]string{
|
||||
"redirect": "/",
|
||||
"alert": c.alert2Str(&structs.AlertInfo{
|
||||
Title: "Server Error",
|
||||
Description: err.Error(),
|
||||
}),
|
||||
}
|
||||
default:
|
||||
exc.Code = 500
|
||||
exc.Parameters = map[string]string{
|
||||
"alert": c.alert2Str(&structs.AlertInfo{
|
||||
Title: "Server Error",
|
||||
Description: err.Error(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return exc
|
||||
}
|
||||
|
||||
func (c *CoreService) checkRequest(ctx context.Context, v any) (err error) {
|
||||
err = c.vd.StructCtx(ctx, v)
|
||||
if err != nil {
|
||||
exc := exceptions.NewCoreServicesException()
|
||||
exc.Code = 400
|
||||
exc.Message = err.Error()
|
||||
exc.Parameters = map[string]string{
|
||||
"alert": c.alert2Str(&structs.AlertInfo{
|
||||
Title: "Validation Error",
|
||||
Description: c.err2str(err),
|
||||
}),
|
||||
}
|
||||
err = exc
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) passwordHasher(plain string) string {
|
||||
return passwordHasher(plain, c.secret)
|
||||
}
|
||||
|
||||
func (c *CoreService) err2str(err error) (s string) {
|
||||
var ve validator.ValidationErrors
|
||||
if errors.As(err, &ve) {
|
||||
var sb strings.Builder
|
||||
for i, f := range ve {
|
||||
sb.WriteString(f.Field())
|
||||
sb.Write([]byte(` `))
|
||||
sb.WriteString(f.Tag())
|
||||
if i+1 < len(ve) {
|
||||
sb.Write([]byte(`, `))
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// pagination2Offset correct pagination and return the offset
|
||||
func (c *CoreService) pagination2Offset(pagination *structs.Pagination) (offset int, limit int) {
|
||||
offset, limit = 0, 25
|
||||
if pagination == nil {
|
||||
return
|
||||
}
|
||||
if pagination.Page <= 0 {
|
||||
pagination.Page = 1
|
||||
}
|
||||
switch {
|
||||
case pagination.RowsPerPage > 100:
|
||||
pagination.RowsPerPage = 100
|
||||
case pagination.RowsPerPage <= 0:
|
||||
pagination.RowsPerPage = int64(limit)
|
||||
}
|
||||
limit = int(pagination.RowsPerPage)
|
||||
offset = int(pagination.Page-1) * limit
|
||||
return
|
||||
}
|
||||
|
||||
// ScPaginate gorm scope function
|
||||
func (c *CoreService) ScPaginate(pagination *structs.Pagination) scopeFunc {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
offset, limit := c.pagination2Offset(pagination)
|
||||
// use id > ? next time.
|
||||
return db.Limit(limit).Offset(offset)
|
||||
}
|
||||
}
|
||||
|
||||
// ScSearchTerm gorm scope function
|
||||
func (c *CoreService) ScSearchTerm(searchTerm string, fields ...string) scopeFunc {
|
||||
// bloat FTS-like search :(
|
||||
var sb strings.Builder
|
||||
var values []any
|
||||
|
||||
if len(fields) <= 0 {
|
||||
goto ret
|
||||
}
|
||||
for i, field := range fields {
|
||||
fmt.Fprintf(&sb, "%s LIKE ?", field)
|
||||
if i < len(fields)-1 {
|
||||
sb.WriteString(" OR ")
|
||||
}
|
||||
values = append(values, fmt.Sprintf("%%%s%%", searchTerm))
|
||||
}
|
||||
|
||||
ret:
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where(sb.String(), values...)
|
||||
}
|
||||
}
|
||||
94
internal/biz/core/default.go
Normal file
94
internal/biz/core/default.go
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
package coresvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"wpw-common/internal/system"
|
||||
"wpw-common/pkg/gen/biz/core/structs"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (c *CoreService) migrateDefaultData() [][]any {
|
||||
mustNot := func(err error) {
|
||||
if err != nil {
|
||||
c.logger.Fatalw("must no error", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// user admin
|
||||
priUser1, pubUser1, err := c.getEd25519KeyPair()
|
||||
mustNot(err)
|
||||
|
||||
var testUser []any
|
||||
for i := 0; i < 100; i++ {
|
||||
testUser = append(testUser, &structs.User{
|
||||
Username: fmt.Sprintf("psw_user%d", i),
|
||||
DisplayName: fmt.Sprintf("The User %d", i),
|
||||
Roles: []*structs.Role{},
|
||||
})
|
||||
}
|
||||
|
||||
var testWp []any
|
||||
for i := 0; i < 200; i++ {
|
||||
testWp = append(testWp, &structs.WajibPajak{
|
||||
Profile: &structs.WajibPajakProfile{
|
||||
// not validated.
|
||||
Npwp: fmt.Sprintf("%15d", i),
|
||||
DisplayName: fmt.Sprintf("PT. Wajib Pajak %d Tbk.", i),
|
||||
Address: fmt.Sprintf("Lorem Ipsum %d, Surabaya, East Java", i),
|
||||
},
|
||||
Owners: []*structs.User{testUser[i/2].(*structs.User)},
|
||||
})
|
||||
}
|
||||
|
||||
return [][]any{
|
||||
// User
|
||||
append([]any{
|
||||
&structs.User{
|
||||
Username: "admin",
|
||||
Password: c.passwordHasher("admin512"),
|
||||
DisplayName: "The Admin",
|
||||
Roles: []*structs.Role{
|
||||
{ID: 1, DisplayName: "admin", RoleType: structs.RoleType_SYSTEM},
|
||||
{ID: 2, DisplayName: "operator", RoleType: structs.RoleType_SYSTEM},
|
||||
},
|
||||
|
||||
PrivateKey: priUser1,
|
||||
PublicKey: pubUser1,
|
||||
},
|
||||
}, testUser...),
|
||||
// // Wajib Pajak
|
||||
append([]any{
|
||||
// &structs.WajibPajak{},
|
||||
}, testWp...),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CoreService) migrateSchemeAndData() {
|
||||
system.Default.DoOnce("core_v1_migrate", func() error {
|
||||
c.logger.Info("migrate scheme and data")
|
||||
err := c.db.Transaction(func(tx *gorm.DB) (err error) {
|
||||
for _, migKinds := range c.migrateDefaultData() {
|
||||
if len(migKinds) <= 0 {
|
||||
continue
|
||||
}
|
||||
if err = tx.AutoMigrate(migKinds[0]); err != nil {
|
||||
return
|
||||
}
|
||||
for _, mData := range migKinds {
|
||||
if err = tx.Create(mData).Error; err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
// fail immediately if the migration fails
|
||||
if err != nil {
|
||||
c.logger.Fatalw("failed to migrate",
|
||||
"err", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
117
internal/biz/core/handler_auth.go
Normal file
117
internal/biz/core/handler_auth.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package coresvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"wpw-common/pkg/gen/biz/core"
|
||||
"wpw-common/pkg/gen/biz/core/structs"
|
||||
|
||||
"github.com/gofiber/fiber/v2/middleware/session"
|
||||
)
|
||||
|
||||
var _ core.CoreService = (*CoreService)(nil)
|
||||
|
||||
func (c *CoreService) Login(ctx context.Context, request *structs.LoginRequest) (r *structs.LoginResponse, err error) {
|
||||
c.logger.Infow("login request", "req", request)
|
||||
// alert := &structs.AlertInfo{}
|
||||
|
||||
// get session
|
||||
var sess *session.Session
|
||||
if sess, err = c.getSession(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
var userSession *structs.User
|
||||
if userSession, err = c.checkSession(sess); err == ErrNotAuthenticated {
|
||||
// login method don't need to be authenticated.
|
||||
goto enter
|
||||
} else if userSession != nil {
|
||||
err = c.wrapServiceError(ErrInvalidState)
|
||||
return
|
||||
} else if err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
enter:
|
||||
// validate the request
|
||||
if err = c.checkRequest(ctx, request); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// hash user psw input
|
||||
var cipPsw = c.passwordHasher(request.Password)
|
||||
|
||||
var user structs.User
|
||||
user.Username = request.Username
|
||||
user.Password = cipPsw
|
||||
|
||||
// lookup for username and hashed psw input
|
||||
tx := c.db.Where(&user).Preload("Roles").First(&user)
|
||||
if err = tx.Error; err != nil {
|
||||
err = c.wrapServiceError(err, "Login Error", "Username or password is incorrect")
|
||||
return
|
||||
}
|
||||
|
||||
r = structs.NewLoginResponse()
|
||||
|
||||
r.TrkToken = sess.ID()
|
||||
// generate jwt token
|
||||
if r.Token, err = c.generateJwtTokenForUser(&user, sess.ID()); err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
// strip confidental user data
|
||||
c.stripConfidentialUserData(&user)
|
||||
|
||||
r.Profile = &user
|
||||
|
||||
// send track session token
|
||||
sess.Set("user", &user)
|
||||
if err = sess.Save(); err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) Logout(ctx context.Context, request *structs.LogoutRequest) (r *structs.LogoutResponse, err error) {
|
||||
c.logger.Infow("logout request", "req", request)
|
||||
// get session
|
||||
var sess *session.Session
|
||||
if sess, err = c.getSession(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
// check session
|
||||
var user *structs.User
|
||||
if user, err = c.checkSession(sess); err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
c.logger.Infow("user logout", "user", user)
|
||||
|
||||
if err = sess.Destroy(); err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
r = structs.NewLogoutResponse()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) GetProfile(ctx context.Context, request *structs.GetProfileRequest) (r *structs.GetProfileResponse, err error) {
|
||||
c.logger.Infow("get profile request", "req", request)
|
||||
var sess *session.Session
|
||||
if sess, err = c.getSession(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var user *structs.User
|
||||
if user, err = c.checkSession(sess); err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
r = structs.NewGetProfileResponse()
|
||||
r.Profile = user
|
||||
return
|
||||
}
|
||||
160
internal/biz/core/handler_users.go
Normal file
160
internal/biz/core/handler_users.go
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
package coresvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"wpw-common/pkg/gen/biz/core"
|
||||
"wpw-common/pkg/gen/biz/core/structs"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var _ core.CoreService = (*CoreService)(nil)
|
||||
|
||||
func (c *CoreService) GetUserList(ctx context.Context, request *structs.GetUserListRequest) (r *structs.GetUserListResponse, err error) {
|
||||
c.logger.Infow("get users request", "req", request)
|
||||
var user *structs.User
|
||||
if user, err = c.getAndCheckSessionAdmin(ctx); err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
_ = user
|
||||
|
||||
if err = c.checkRequest(ctx, request); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r = structs.NewGetUserListResponse()
|
||||
r.Pagination = request.Pagination
|
||||
|
||||
// query scopes
|
||||
scopes := []scopeFunc{
|
||||
c.ScPaginate(request.Pagination),
|
||||
}
|
||||
|
||||
// using search term
|
||||
if searchTerm := request.SearchTerm; searchTerm != "" {
|
||||
scopes = append(scopes, c.ScSearchTerm(request.SearchTerm,
|
||||
"username", "display_name"))
|
||||
}
|
||||
|
||||
// get users
|
||||
tx := c.db.Scopes(scopes...).
|
||||
Preload("Roles").
|
||||
Find(&r.Users)
|
||||
if err = tx.Error; err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// get total users count
|
||||
tx = c.db.Model(structs.User{}).Count(&r.TotalUsers)
|
||||
if err = tx.Error; err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// strip users data
|
||||
for _, user := range r.Users {
|
||||
c.stripConfidentialUserData(user)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) CreateUser(ctx context.Context, request *structs.CreateUserRequest) (r *structs.CreateUserResponse, err error) {
|
||||
c.logger.Infow("create user request", "req", request)
|
||||
var user *structs.User
|
||||
if user, err = c.getAndCheckSessionAdmin(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
_ = user
|
||||
|
||||
// validate
|
||||
requestUser := request.User
|
||||
if err = c.checkRequest(ctx, requestUser); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
daoUser := structs.NewUser()
|
||||
// pass necessary data.
|
||||
daoUser.Username = requestUser.Username
|
||||
daoUser.Password = c.passwordHasher(requestUser.Password)
|
||||
daoUser.DisplayName = requestUser.DisplayName
|
||||
|
||||
var (
|
||||
pri ed25519.PrivateKey
|
||||
pub ed25519.PublicKey
|
||||
)
|
||||
if pri, pub, err = c.getEd25519KeyPair(); err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
daoUser.PrivateKey = pri
|
||||
daoUser.PublicKey = pub
|
||||
|
||||
tx := c.db.Create(daoUser)
|
||||
if err = tx.Error; err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
r = structs.NewCreateUserResponse()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) DeleteUsers(ctx context.Context, request *structs.DeleteUsersRequest) (r *structs.DeleteUsersResponse, err error) {
|
||||
c.logger.Infow("delete users request", "req", request)
|
||||
var user *structs.User
|
||||
if user, err = c.getAndCheckSessionAdmin(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
_ = user
|
||||
|
||||
if err = c.checkRequest(ctx, request); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var targetUsers []*structs.User
|
||||
tx := c.db.
|
||||
Where("id IN ?", request.UserIds).Select("id").
|
||||
Preload("Roles").
|
||||
Find(&targetUsers)
|
||||
if err = tx.Error; err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
var qualifiedTargetUsers []*structs.User
|
||||
var (
|
||||
idsSuccess []int64
|
||||
idsIgnored []int64
|
||||
)
|
||||
err = c.db.Transaction(func(tx *gorm.DB) (err error) {
|
||||
for _, targetUser := range targetUsers {
|
||||
if targetUser.ID == user.ID || c.isSystemUser(targetUser) {
|
||||
idsIgnored = append(idsIgnored, targetUser.ID)
|
||||
continue
|
||||
}
|
||||
qualifiedTargetUsers = append(qualifiedTargetUsers, targetUser)
|
||||
idsSuccess = append(idsSuccess, targetUser.ID)
|
||||
}
|
||||
if len(qualifiedTargetUsers) <= 0 {
|
||||
// has nothing to do with.
|
||||
return
|
||||
}
|
||||
tx = tx.Delete(qualifiedTargetUsers)
|
||||
if err = tx.Error; err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
r = structs.NewDeleteUsersResponse()
|
||||
r.Success = idsSuccess
|
||||
r.Ignored = idsIgnored
|
||||
return
|
||||
}
|
||||
181
internal/biz/core/handler_wajib_pajak.go
Normal file
181
internal/biz/core/handler_wajib_pajak.go
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
package coresvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"wpw-common/pkg/gen/biz/core"
|
||||
"wpw-common/pkg/gen/biz/core/structs"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var _ core.CoreService = (*CoreService)(nil)
|
||||
|
||||
func (c *CoreService) GetWajibPajakList(ctx context.Context, request *structs.GetWajibPajakListRequest) (r *structs.GetWajibPajakListResponse, err error) {
|
||||
c.logger.Infow("get wajib pajak request", "req", request)
|
||||
var user *structs.User
|
||||
if user, err = c.getAndCheckSession(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
_ = user
|
||||
|
||||
if err = c.checkRequest(ctx, request); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r = structs.NewGetWajibPajakListResponse()
|
||||
r.Pagination = request.Pagination
|
||||
|
||||
// query scopes
|
||||
scopes := []scopeFunc{
|
||||
c.ScPaginate(request.Pagination),
|
||||
}
|
||||
|
||||
// using search term
|
||||
if searchTerm := request.SearchTerm; searchTerm != "" {
|
||||
scopes = append(scopes, c.ScSearchTerm(request.SearchTerm,
|
||||
"profile_npwp", "profile_display_name", "profile_address"))
|
||||
}
|
||||
|
||||
// admin user, ignoring the `ownership`
|
||||
if c.isSystemUser(user) {
|
||||
// get all wps
|
||||
tx := c.db.Scopes(scopes...).
|
||||
Find(&r.WajibPajakList)
|
||||
if err = tx.Error; err != nil {
|
||||
return
|
||||
}
|
||||
// get all wps count
|
||||
tx = c.db.Model(structs.WajibPajak{}).Count(&r.TotalWajibPajak)
|
||||
if err = tx.Error; err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// normal user
|
||||
var tx *gorm.DB
|
||||
switch request.Ownership {
|
||||
case structs.WajibPajakOwnership_OWNED:
|
||||
tx = c.db.
|
||||
Preload("OwnedWajibPajakList").
|
||||
Where(user).
|
||||
First(&user)
|
||||
if err = tx.Error; err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
r.WajibPajakList = user.OwnedWajibPajakList
|
||||
r.TotalWajibPajak = int64(len(user.OwnedWajibPajakList)) // TODO: temporary
|
||||
case structs.WajibPajakOwnership_ROLE:
|
||||
tx = c.db.
|
||||
Preload("RolesWajibPajakList").
|
||||
Preload("RolesWajibPajakList.WajibPajakList").
|
||||
Where(user).First(&user)
|
||||
if err = tx.Error; err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
for _, role := range user.Roles {
|
||||
r.WajibPajakList = append(r.WajibPajakList, role.WajibPajakList...)
|
||||
}
|
||||
r.TotalWajibPajak = int64(len(r.WajibPajakList)) // TODO: temporary
|
||||
default:
|
||||
err = c.wrapServiceError(ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) CreateWajibPajak(ctx context.Context, request *structs.CreateWajibPajakRequest) (r *structs.CreateWajibPajakResponse, err error) {
|
||||
c.logger.Infow("create wajib pajajk request", "req", request)
|
||||
var user *structs.User
|
||||
if user, err = c.getAndCheckSession(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
_ = user
|
||||
|
||||
if err = c.checkRequest(ctx, request); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var wp structs.WajibPajak
|
||||
wp.Profile = request.WajibPajak.Profile
|
||||
wp.Owners = []*structs.User{
|
||||
// set authenticated user as the ownner of the Wajib Pajak
|
||||
user,
|
||||
}
|
||||
|
||||
tx := c.db.Create(&wp)
|
||||
if err = tx.Error; err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
r = structs.NewCreateWajibPajakResponse()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CoreService) DeleteWajibPajakList(ctx context.Context, request *structs.DeleteWajibpajakListRequest) (r *structs.DeleteWajibpajakListResponse, err error) {
|
||||
c.logger.Infow("delete wajib pajak request", "req", request)
|
||||
var user *structs.User
|
||||
if user, err = c.getAndCheckSession(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
_ = user
|
||||
|
||||
if err = c.checkRequest(ctx, request); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var targetWps []*structs.WajibPajak
|
||||
tx := c.db.
|
||||
Where("id IN ?", request.WpIds).
|
||||
Select("id").
|
||||
Preload("Owners").
|
||||
Find(&targetWps)
|
||||
if err = tx.Error; err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
qsTargetWps []*structs.WajibPajak
|
||||
idsSuccess []int64
|
||||
idsIgnored []int64
|
||||
)
|
||||
|
||||
err = c.db.Transaction(func(tx *gorm.DB) (err error) {
|
||||
for _, targetWp := range targetWps {
|
||||
var found bool
|
||||
for _, owner := range targetWp.Owners {
|
||||
if owner.ID == user.ID {
|
||||
qsTargetWps = append(qsTargetWps, targetWp)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
idsSuccess = append(idsSuccess, targetWp.ID)
|
||||
} else {
|
||||
idsIgnored = append(idsIgnored, targetWp.ID)
|
||||
}
|
||||
}
|
||||
tx = tx.Delete(qsTargetWps)
|
||||
if err = tx.Error; err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
err = c.wrapServiceError(err)
|
||||
return
|
||||
}
|
||||
|
||||
r = structs.NewDeleteWajibpajakListResponse()
|
||||
r.Success = idsSuccess
|
||||
r.Ignored = idsIgnored
|
||||
return
|
||||
}
|
||||
18
internal/biz/core/util.go
Normal file
18
internal/biz/core/util.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package coresvc
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
var (
|
||||
pswIter = 2 << 11
|
||||
pswLength = 50 // raw length
|
||||
)
|
||||
|
||||
func passwordHasher(plain string, salt []byte) string {
|
||||
psw := pbkdf2.Key([]byte(plain), salt, pswIter, pswLength, sha256.New)
|
||||
return base64.StdEncoding.EncodeToString(psw)
|
||||
}
|
||||
84
internal/context.go
Normal file
84
internal/context.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
var (
|
||||
contextKeyApp contextKey = "application-context"
|
||||
)
|
||||
|
||||
func WithAppContext(ctx context.Context, appCtx *AppContext) context.Context {
|
||||
appCtx.parent = ctx
|
||||
return context.WithValue(ctx, contextKeyApp, appCtx)
|
||||
}
|
||||
|
||||
func GetAppContext(ctx context.Context) *AppContext {
|
||||
return ctx.Value(contextKeyApp).(*AppContext)
|
||||
}
|
||||
|
||||
type AppContext struct {
|
||||
parent context.Context
|
||||
|
||||
Logger *zap.Logger
|
||||
Data map[string]any
|
||||
Cancel context.CancelFunc
|
||||
|
||||
task []task
|
||||
taskCnt atomic.Int32
|
||||
taskDone atomic.Int32
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func (c AppContext) Init() AppContext {
|
||||
c.Data = make(map[string]any)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *AppContext) GetContext() context.Context {
|
||||
ctx, cancel := context.WithCancel(c.parent)
|
||||
c.task = append(c.task, task{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
})
|
||||
return ctx
|
||||
}
|
||||
|
||||
// DoTask spawn task on a goroutine
|
||||
func (p *AppContext) DoTask(f func(ctx context.Context)) {
|
||||
ctx := p.GetContext()
|
||||
p.wg.Add(1)
|
||||
p.taskCnt.Add(1)
|
||||
go func() {
|
||||
defer p.wg.Done()
|
||||
defer p.taskDone.Add(1)
|
||||
f(ctx)
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *AppContext) TaskCount() int {
|
||||
return int(p.taskCnt.Load())
|
||||
}
|
||||
|
||||
func (p *AppContext) TaskDone() int {
|
||||
return int(p.taskDone.Load())
|
||||
}
|
||||
|
||||
func (p *AppContext) TaskRemaining() int {
|
||||
return p.TaskCount() - p.TaskDone()
|
||||
}
|
||||
|
||||
func (p *AppContext) WaitTask() {
|
||||
p.wg.Wait()
|
||||
}
|
||||
|
||||
type task struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
167
internal/http/server.go
Normal file
167
internal/http/server.go
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"wpw-common/internal"
|
||||
"wpw-common/public"
|
||||
|
||||
"github.com/apache/thrift/lib/go/thrift"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/filesystem"
|
||||
"github.com/gofiber/fiber/v2/middleware/recover"
|
||||
"github.com/gofiber/fiber/v2/middleware/session"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultServer = newServer()
|
||||
Session = session.New(session.Config{
|
||||
KeyLookup: "header:X-Hub-Track-Token",
|
||||
})
|
||||
|
||||
thriftContentType = []byte("application/x-thrift")
|
||||
|
||||
contextKeyFiber contextKey = "fiber-app"
|
||||
|
||||
hubHeader = []string{
|
||||
"X-Hub-Env",
|
||||
"X-Hub-App",
|
||||
"X-Hub-Version",
|
||||
"X-Hub-App",
|
||||
"X-Hub-Track-Token",
|
||||
"X-Hub-Auth-Token",
|
||||
}
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
type server struct {
|
||||
pCtx context.Context
|
||||
app *fiber.App
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func newServer() *server {
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(recover.New())
|
||||
|
||||
app.Use(cors.New(cors.Config{
|
||||
// AllowOrigins: "https://gofiber.io, https://gofiber.net",
|
||||
AllowOrigins: "*",
|
||||
AllowHeaders: strings.Join(append([]string{
|
||||
"Origin",
|
||||
"Content-Type",
|
||||
"Accept",
|
||||
}, hubHeader...), ", "),
|
||||
ExposeHeaders: strings.Join(hubHeader, ", "),
|
||||
}))
|
||||
|
||||
app.Use(filesystem.New(filesystem.Config{
|
||||
Root: http.FS(public.FS),
|
||||
Browse: false,
|
||||
Index: "index.html",
|
||||
MaxAge: 3000,
|
||||
NotFoundFile: "index.html",
|
||||
}))
|
||||
|
||||
srv := &server{app: app}
|
||||
return srv
|
||||
}
|
||||
|
||||
func (s *server) registerService(path string, p thrift.TProcessor) {
|
||||
processorFactory := thrift.NewTProcessorFactory(p)
|
||||
protocolFactory := thrift.NewTCompactProtocolFactoryConf(&thrift.TConfiguration{})
|
||||
contentTypeResp := mime.FormatMediaType(string(thriftContentType), map[string]string{
|
||||
"charset": "utf-8",
|
||||
"protocol": "TCOMPACT",
|
||||
})
|
||||
|
||||
s.app.Post(path, func(c *fiber.Ctx) error {
|
||||
mBegin := time.Now()
|
||||
var mDur time.Duration
|
||||
|
||||
c.Response().Header.Set("Content-Type", contentTypeResp)
|
||||
if !bytes.HasPrefix(c.Request().Header.ContentType(), thriftContentType) {
|
||||
c.Response().Header.Set("x-error-type", "bad-request")
|
||||
c.Response().Header.Set("x-error-message", "bad request")
|
||||
return c.SendStatus(400)
|
||||
}
|
||||
|
||||
body := c.Context().Request.Body()
|
||||
transport := thrift.NewStreamTransport(bytes.NewReader(body), c)
|
||||
protocol := protocolFactory.GetProtocol(transport)
|
||||
|
||||
// put fiber ctx to the context
|
||||
ctx := WithFiberContext(s.pCtx, c)
|
||||
|
||||
// process method
|
||||
handled, err := processorFactory.GetProcessor(transport).Process(ctx, protocol, protocol)
|
||||
if err != nil {
|
||||
s.logger.Errorw("process method", "err", err)
|
||||
switch err.(type) {
|
||||
case thrift.TTransportException:
|
||||
c.Response().Header.Set("x-error-type", "transport-exception")
|
||||
c.Response().Header.Set("x-error-message", err.Error())
|
||||
goto internal_error
|
||||
case thrift.TProtocolException:
|
||||
c.Response().Header.Set("x-error-type", "protocol-exception")
|
||||
c.Response().Header.Set("x-error-message", err.Error())
|
||||
goto internal_error
|
||||
case thrift.TApplicationException:
|
||||
c.Response().Header.Set("x-error-type", "application-exception")
|
||||
c.Response().Header.Set("x-error-message", err.Error())
|
||||
default:
|
||||
c.Response().Header.Set("x-error-type", "exception")
|
||||
c.Response().Header.Set("x-error-message", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
mDur = time.Since(mBegin)
|
||||
c.Response().Header.Set("x-trace-duration", mDur.String())
|
||||
|
||||
_ = handled
|
||||
if err := protocol.Flush(c.Context()); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SendStatus(200)
|
||||
internal_error:
|
||||
// do something!
|
||||
return c.SendStatus(200)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *server) run(appCtx *internal.AppContext, addr string) error {
|
||||
s.pCtx = internal.WithAppContext(context.Background(), appCtx)
|
||||
s.logger = appCtx.Logger.Sugar().Named("httpsrv")
|
||||
s.logger.Infow("starting server",
|
||||
"addr", addr)
|
||||
return s.app.Listen(addr)
|
||||
}
|
||||
|
||||
func Router() *fiber.App {
|
||||
return defaultServer.app
|
||||
}
|
||||
|
||||
func GetFiberFromContext(ctx context.Context) *fiber.Ctx {
|
||||
return ctx.Value(contextKeyFiber).(*fiber.Ctx)
|
||||
}
|
||||
|
||||
func WithFiberContext(ctx context.Context, c *fiber.Ctx) context.Context {
|
||||
return context.WithValue(ctx, contextKeyFiber, c)
|
||||
}
|
||||
|
||||
func RegisterService(path string, p thrift.TProcessor) {
|
||||
defaultServer.registerService(path, p)
|
||||
}
|
||||
|
||||
func Run(appCtx *internal.AppContext, addr string) error {
|
||||
return defaultServer.run(appCtx, addr)
|
||||
}
|
||||
61
internal/system/system.go
Normal file
61
internal/system/system.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"wpw-common/internal"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SystemKV struct {
|
||||
gorm.Model
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
type system struct {
|
||||
logger *zap.SugaredLogger
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
var Default system
|
||||
|
||||
func (s *system) init(appCtx *internal.AppContext) {
|
||||
logger := appCtx.Logger.Sugar().Named("system")
|
||||
db, exist := appCtx.Data["db"]
|
||||
if !exist {
|
||||
logger.Fatal("system requires `db` in application context")
|
||||
}
|
||||
s.logger = logger
|
||||
s.db = db.(*gorm.DB)
|
||||
s.db.AutoMigrate(&SystemKV{})
|
||||
}
|
||||
|
||||
func (s *system) DoOnce(key string, fn func() error) (err error) {
|
||||
var kv SystemKV
|
||||
kv.Key = key
|
||||
tx := s.db.First(&kv)
|
||||
if err = tx.Error; err == gorm.ErrRecordNotFound {
|
||||
s.logger.Info("doOnce call",
|
||||
"key", key)
|
||||
if err = fn(); err != nil {
|
||||
|
||||
return
|
||||
}
|
||||
tx = s.db.Create(&kv)
|
||||
if err = tx.Error; err != nil {
|
||||
return
|
||||
}
|
||||
} else if err == nil {
|
||||
s.logger.Infow("doOnce done",
|
||||
"key", key,
|
||||
"at", kv.CreatedAt)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Init(appCtx *internal.AppContext) {
|
||||
Default.init(appCtx)
|
||||
}
|
||||
31
internal/vdext/validator.go
Normal file
31
internal/vdext/validator.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package vdext
|
||||
|
||||
import (
|
||||
"github.com/go-playground/locales"
|
||||
"github.com/go-playground/locales/en"
|
||||
uts "github.com/go-playground/universal-translator"
|
||||
"github.com/go-playground/validator/v10"
|
||||
en_translations "github.com/go-playground/validator/v10/translations/en"
|
||||
)
|
||||
|
||||
var (
|
||||
languages = []locales.Translator{
|
||||
en.New(),
|
||||
}
|
||||
uni = initI8n()
|
||||
)
|
||||
|
||||
func initI8n() *uts.UniversalTranslator {
|
||||
return uts.New(languages[0], languages[1:]...)
|
||||
}
|
||||
|
||||
func New(tagName string) *validator.Validate {
|
||||
vd := validator.New()
|
||||
|
||||
if trans, found := uni.GetTranslator("en"); found {
|
||||
en_translations.RegisterDefaultTranslations(vd, trans)
|
||||
}
|
||||
|
||||
vd.SetTagName(tagName)
|
||||
return vd
|
||||
}
|
||||
330
pkg/gen/biz/core/exceptions/exceptions.go
Normal file
330
pkg/gen/biz/core/exceptions/exceptions.go
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
// Code generated by thriftgo (0.2.1). DO NOT EDIT.
|
||||
|
||||
package exceptions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/apache/thrift/lib/go/thrift"
|
||||
"github.com/cloudwego/thriftgo/generator/golang/extension/meta"
|
||||
"github.com/cloudwego/thriftgo/generator/golang/extension/unknown"
|
||||
)
|
||||
|
||||
type CoreServicesException struct {
|
||||
Code int32 `thrift:"code,1" frugal:"1,default,i32" db:"code" json:"code"`
|
||||
Message string `thrift:"message,2" frugal:"2,default,string" db:"message" json:"message"`
|
||||
Parameters map[string]string `thrift:"parameters,3" frugal:"3,default,map<string:string>" db:"parameters" json:"parameters"`
|
||||
_unknownFields unknown.Fields
|
||||
}
|
||||
|
||||
func init() {
|
||||
meta.RegisterStruct(NewCoreServicesException, []byte{
|
||||
0xb, 0x0, 0x1, 0x0, 0x0, 0x0, 0x15, 0x43,
|
||||
0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x73, 0x45, 0x78, 0x63, 0x65, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0xb, 0x0, 0x2, 0x0,
|
||||
0x0, 0x0, 0x9, 0x65, 0x78, 0x63, 0x65, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0xf, 0x0, 0x3, 0xc,
|
||||
0x0, 0x0, 0x0, 0x3, 0x6, 0x0, 0x1, 0x0,
|
||||
0x1, 0xb, 0x0, 0x2, 0x0, 0x0, 0x0, 0x4,
|
||||
0x63, 0x6f, 0x64, 0x65, 0x8, 0x0, 0x3, 0x0,
|
||||
0x0, 0x0, 0x0, 0xc, 0x0, 0x4, 0x8, 0x0,
|
||||
0x1, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x6,
|
||||
0x0, 0x1, 0x0, 0x2, 0xb, 0x0, 0x2, 0x0,
|
||||
0x0, 0x0, 0x7, 0x6d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x8, 0x0, 0x3, 0x0, 0x0, 0x0,
|
||||
0x0, 0xc, 0x0, 0x4, 0x8, 0x0, 0x1, 0x0,
|
||||
0x0, 0x0, 0xb, 0x0, 0x0, 0x6, 0x0, 0x1,
|
||||
0x0, 0x3, 0xb, 0x0, 0x2, 0x0, 0x0, 0x0,
|
||||
0xa, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74,
|
||||
0x65, 0x72, 0x73, 0x8, 0x0, 0x3, 0x0, 0x0,
|
||||
0x0, 0x0, 0xc, 0x0, 0x4, 0x8, 0x0, 0x1,
|
||||
0x0, 0x0, 0x0, 0xd, 0xc, 0x0, 0x2, 0x8,
|
||||
0x0, 0x1, 0x0, 0x0, 0x0, 0xb, 0x0, 0xc,
|
||||
0x0, 0x3, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0,
|
||||
0xb, 0x0, 0x0, 0x0, 0x0,
|
||||
})
|
||||
}
|
||||
|
||||
func NewCoreServicesException() *CoreServicesException {
|
||||
return &CoreServicesException{}
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) InitDefault() {
|
||||
*p = CoreServicesException{}
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) GetCode() (v int32) {
|
||||
return p.Code
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) GetMessage() (v string) {
|
||||
return p.Message
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) GetParameters() (v map[string]string) {
|
||||
return p.Parameters
|
||||
}
|
||||
func (p *CoreServicesException) SetCode(val int32) {
|
||||
p.Code = val
|
||||
}
|
||||
func (p *CoreServicesException) SetMessage(val string) {
|
||||
p.Message = val
|
||||
}
|
||||
func (p *CoreServicesException) SetParameters(val map[string]string) {
|
||||
p.Parameters = val
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) CarryingUnknownFields() bool {
|
||||
return len(p._unknownFields) > 0
|
||||
}
|
||||
|
||||
var fieldIDToName_CoreServicesException = map[int16]string{
|
||||
1: "code",
|
||||
2: "message",
|
||||
3: "parameters",
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) Read(ctx context.Context, iprot thrift.TProtocol) (err error) {
|
||||
var name string
|
||||
var fieldTypeId thrift.TType
|
||||
var fieldId int16
|
||||
|
||||
if _, err = iprot.ReadStructBegin(ctx); err != nil {
|
||||
goto ReadStructBeginError
|
||||
}
|
||||
|
||||
for {
|
||||
name, fieldTypeId, fieldId, err = iprot.ReadFieldBegin(ctx)
|
||||
if err != nil {
|
||||
goto ReadFieldBeginError
|
||||
}
|
||||
if fieldTypeId == thrift.STOP {
|
||||
break
|
||||
}
|
||||
|
||||
switch fieldId {
|
||||
case 1:
|
||||
if fieldTypeId == thrift.I32 {
|
||||
if err = p.ReadField1(ctx, iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else {
|
||||
if err = iprot.Skip(ctx, fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if fieldTypeId == thrift.STRING {
|
||||
if err = p.ReadField2(ctx, iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else {
|
||||
if err = iprot.Skip(ctx, fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
if fieldTypeId == thrift.MAP {
|
||||
if err = p.ReadField3(ctx, iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else {
|
||||
if err = iprot.Skip(ctx, fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
}
|
||||
default:
|
||||
if err = p._unknownFields.Append(ctx, iprot, name, fieldTypeId, fieldId); err != nil {
|
||||
goto UnknownFieldsAppendError
|
||||
}
|
||||
}
|
||||
|
||||
if err = iprot.ReadFieldEnd(ctx); err != nil {
|
||||
goto ReadFieldEndError
|
||||
}
|
||||
}
|
||||
if err = iprot.ReadStructEnd(ctx); err != nil {
|
||||
goto ReadStructEndError
|
||||
}
|
||||
|
||||
return nil
|
||||
ReadStructBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
|
||||
ReadFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
|
||||
ReadFieldError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_CoreServicesException[fieldId]), err)
|
||||
SkipFieldError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
|
||||
UnknownFieldsAppendError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T append unknown field(name:%s type:%d id:%d) error: ", p, name, fieldTypeId, fieldId), err)
|
||||
|
||||
ReadFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
|
||||
ReadStructEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) ReadField1(ctx context.Context, iprot thrift.TProtocol) error {
|
||||
if v, err := iprot.ReadI32(ctx); err != nil {
|
||||
return err
|
||||
} else {
|
||||
p.Code = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) ReadField2(ctx context.Context, iprot thrift.TProtocol) error {
|
||||
if v, err := iprot.ReadString(ctx); err != nil {
|
||||
return err
|
||||
} else {
|
||||
p.Message = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) ReadField3(ctx context.Context, iprot thrift.TProtocol) error {
|
||||
_, _, size, err := iprot.ReadMapBegin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Parameters = make(map[string]string, size)
|
||||
for i := 0; i < size; i++ {
|
||||
var _key string
|
||||
if v, err := iprot.ReadString(ctx); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_key = v
|
||||
}
|
||||
|
||||
var _val string
|
||||
if v, err := iprot.ReadString(ctx); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_val = v
|
||||
}
|
||||
|
||||
p.Parameters[_key] = _val
|
||||
}
|
||||
if err := iprot.ReadMapEnd(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) Write(ctx context.Context, oprot thrift.TProtocol) (err error) {
|
||||
var fieldId int16
|
||||
if err = oprot.WriteStructBegin(ctx, "CoreServicesException"); err != nil {
|
||||
goto WriteStructBeginError
|
||||
}
|
||||
if p != nil {
|
||||
if err = p.writeField1(ctx, oprot); err != nil {
|
||||
fieldId = 1
|
||||
goto WriteFieldError
|
||||
}
|
||||
if err = p.writeField2(ctx, oprot); err != nil {
|
||||
fieldId = 2
|
||||
goto WriteFieldError
|
||||
}
|
||||
if err = p.writeField3(ctx, oprot); err != nil {
|
||||
fieldId = 3
|
||||
goto WriteFieldError
|
||||
}
|
||||
|
||||
if err = p._unknownFields.Write(ctx, oprot); err != nil {
|
||||
goto UnknownFieldsWriteError
|
||||
}
|
||||
}
|
||||
if err = oprot.WriteFieldStop(ctx); err != nil {
|
||||
goto WriteFieldStopError
|
||||
}
|
||||
if err = oprot.WriteStructEnd(ctx); err != nil {
|
||||
goto WriteStructEndError
|
||||
}
|
||||
return nil
|
||||
WriteStructBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
|
||||
WriteFieldError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
|
||||
WriteFieldStopError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
|
||||
WriteStructEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
|
||||
UnknownFieldsWriteError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write unknown fields error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin(ctx, "code", thrift.I32, 1); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteI32(ctx, p.Code); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(ctx); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin(ctx, "message", thrift.STRING, 2); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteString(ctx, p.Message); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(ctx); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) writeField3(ctx context.Context, oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin(ctx, "parameters", thrift.MAP, 3); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteMapBegin(ctx, thrift.STRING, thrift.STRING, len(p.Parameters)); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range p.Parameters {
|
||||
|
||||
if err := oprot.WriteString(ctx, k); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := oprot.WriteString(ctx, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := oprot.WriteMapEnd(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(ctx); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *CoreServicesException) String() string {
|
||||
if p == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return fmt.Sprintf("CoreServicesException(%+v)", *p)
|
||||
}
|
||||
func (p *CoreServicesException) Error() string {
|
||||
return p.String()
|
||||
}
|
||||
4611
pkg/gen/biz/core/service.go
Normal file
4611
pkg/gen/biz/core/service.go
Normal file
File diff suppressed because it is too large
Load diff
6658
pkg/gen/biz/core/structs/structs.go
Normal file
6658
pkg/gen/biz/core/structs/structs.go
Normal file
File diff suppressed because it is too large
Load diff
340
pkg/gen/common/exceptionsc/exceptionsc.go
Normal file
340
pkg/gen/common/exceptionsc/exceptionsc.go
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
// Code generated by thriftgo (0.2.1). DO NOT EDIT.
|
||||
|
||||
package exceptionsc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/apache/thrift/lib/go/thrift"
|
||||
"github.com/cloudwego/thriftgo/generator/golang/extension/meta"
|
||||
"github.com/cloudwego/thriftgo/generator/golang/extension/unknown"
|
||||
)
|
||||
|
||||
type CommonException struct {
|
||||
Code int32 `thrift:"code,1" frugal:"1,default,i32" db:"code" json:"code"`
|
||||
Message string `thrift:"message,2" frugal:"2,default,string" db:"message" json:"message"`
|
||||
Metadata map[string]string `thrift:"metadata,3,optional" frugal:"3,optional,map<string:string>" db:"metadata" json:"metadata,omitempty"`
|
||||
_unknownFields unknown.Fields
|
||||
}
|
||||
|
||||
func init() {
|
||||
meta.RegisterStruct(NewCommonException, []byte{
|
||||
0xb, 0x0, 0x1, 0x0, 0x0, 0x0, 0xf, 0x43,
|
||||
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x45, 0x78, 0x63,
|
||||
0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0xb, 0x0,
|
||||
0x2, 0x0, 0x0, 0x0, 0x9, 0x65, 0x78, 0x63,
|
||||
0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0xf, 0x0,
|
||||
0x3, 0xc, 0x0, 0x0, 0x0, 0x3, 0x6, 0x0,
|
||||
0x1, 0x0, 0x1, 0xb, 0x0, 0x2, 0x0, 0x0,
|
||||
0x0, 0x4, 0x63, 0x6f, 0x64, 0x65, 0x8, 0x0,
|
||||
0x3, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x4,
|
||||
0x8, 0x0, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0,
|
||||
0x0, 0x6, 0x0, 0x1, 0x0, 0x2, 0xb, 0x0,
|
||||
0x2, 0x0, 0x0, 0x0, 0x7, 0x6d, 0x65, 0x73,
|
||||
0x73, 0x61, 0x67, 0x65, 0x8, 0x0, 0x3, 0x0,
|
||||
0x0, 0x0, 0x0, 0xc, 0x0, 0x4, 0x8, 0x0,
|
||||
0x1, 0x0, 0x0, 0x0, 0xb, 0x0, 0x0, 0x6,
|
||||
0x0, 0x1, 0x0, 0x3, 0xb, 0x0, 0x2, 0x0,
|
||||
0x0, 0x0, 0x8, 0x6d, 0x65, 0x74, 0x61, 0x64,
|
||||
0x61, 0x74, 0x61, 0x8, 0x0, 0x3, 0x0, 0x0,
|
||||
0x0, 0x2, 0xc, 0x0, 0x4, 0x8, 0x0, 0x1,
|
||||
0x0, 0x0, 0x0, 0xd, 0xc, 0x0, 0x2, 0x8,
|
||||
0x0, 0x1, 0x0, 0x0, 0x0, 0xb, 0x0, 0xc,
|
||||
0x0, 0x3, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0,
|
||||
0xb, 0x0, 0x0, 0x0, 0x0,
|
||||
})
|
||||
}
|
||||
|
||||
func NewCommonException() *CommonException {
|
||||
return &CommonException{}
|
||||
}
|
||||
|
||||
func (p *CommonException) InitDefault() {
|
||||
*p = CommonException{}
|
||||
}
|
||||
|
||||
func (p *CommonException) GetCode() (v int32) {
|
||||
return p.Code
|
||||
}
|
||||
|
||||
func (p *CommonException) GetMessage() (v string) {
|
||||
return p.Message
|
||||
}
|
||||
|
||||
var CommonException_Metadata_DEFAULT map[string]string
|
||||
|
||||
func (p *CommonException) GetMetadata() (v map[string]string) {
|
||||
if !p.IsSetMetadata() {
|
||||
return CommonException_Metadata_DEFAULT
|
||||
}
|
||||
return p.Metadata
|
||||
}
|
||||
func (p *CommonException) SetCode(val int32) {
|
||||
p.Code = val
|
||||
}
|
||||
func (p *CommonException) SetMessage(val string) {
|
||||
p.Message = val
|
||||
}
|
||||
func (p *CommonException) SetMetadata(val map[string]string) {
|
||||
p.Metadata = val
|
||||
}
|
||||
|
||||
func (p *CommonException) CarryingUnknownFields() bool {
|
||||
return len(p._unknownFields) > 0
|
||||
}
|
||||
|
||||
var fieldIDToName_CommonException = map[int16]string{
|
||||
1: "code",
|
||||
2: "message",
|
||||
3: "metadata",
|
||||
}
|
||||
|
||||
func (p *CommonException) IsSetMetadata() bool {
|
||||
return p.Metadata != nil
|
||||
}
|
||||
|
||||
func (p *CommonException) Read(ctx context.Context, iprot thrift.TProtocol) (err error) {
|
||||
var name string
|
||||
var fieldTypeId thrift.TType
|
||||
var fieldId int16
|
||||
|
||||
if _, err = iprot.ReadStructBegin(ctx); err != nil {
|
||||
goto ReadStructBeginError
|
||||
}
|
||||
|
||||
for {
|
||||
name, fieldTypeId, fieldId, err = iprot.ReadFieldBegin(ctx)
|
||||
if err != nil {
|
||||
goto ReadFieldBeginError
|
||||
}
|
||||
if fieldTypeId == thrift.STOP {
|
||||
break
|
||||
}
|
||||
|
||||
switch fieldId {
|
||||
case 1:
|
||||
if fieldTypeId == thrift.I32 {
|
||||
if err = p.ReadField1(ctx, iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else {
|
||||
if err = iprot.Skip(ctx, fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if fieldTypeId == thrift.STRING {
|
||||
if err = p.ReadField2(ctx, iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else {
|
||||
if err = iprot.Skip(ctx, fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
if fieldTypeId == thrift.MAP {
|
||||
if err = p.ReadField3(ctx, iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else {
|
||||
if err = iprot.Skip(ctx, fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
}
|
||||
default:
|
||||
if err = p._unknownFields.Append(ctx, iprot, name, fieldTypeId, fieldId); err != nil {
|
||||
goto UnknownFieldsAppendError
|
||||
}
|
||||
}
|
||||
|
||||
if err = iprot.ReadFieldEnd(ctx); err != nil {
|
||||
goto ReadFieldEndError
|
||||
}
|
||||
}
|
||||
if err = iprot.ReadStructEnd(ctx); err != nil {
|
||||
goto ReadStructEndError
|
||||
}
|
||||
|
||||
return nil
|
||||
ReadStructBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
|
||||
ReadFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
|
||||
ReadFieldError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_CommonException[fieldId]), err)
|
||||
SkipFieldError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
|
||||
UnknownFieldsAppendError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T append unknown field(name:%s type:%d id:%d) error: ", p, name, fieldTypeId, fieldId), err)
|
||||
|
||||
ReadFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
|
||||
ReadStructEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *CommonException) ReadField1(ctx context.Context, iprot thrift.TProtocol) error {
|
||||
if v, err := iprot.ReadI32(ctx); err != nil {
|
||||
return err
|
||||
} else {
|
||||
p.Code = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *CommonException) ReadField2(ctx context.Context, iprot thrift.TProtocol) error {
|
||||
if v, err := iprot.ReadString(ctx); err != nil {
|
||||
return err
|
||||
} else {
|
||||
p.Message = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *CommonException) ReadField3(ctx context.Context, iprot thrift.TProtocol) error {
|
||||
_, _, size, err := iprot.ReadMapBegin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = make(map[string]string, size)
|
||||
for i := 0; i < size; i++ {
|
||||
var _key string
|
||||
if v, err := iprot.ReadString(ctx); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_key = v
|
||||
}
|
||||
|
||||
var _val string
|
||||
if v, err := iprot.ReadString(ctx); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_val = v
|
||||
}
|
||||
|
||||
p.Metadata[_key] = _val
|
||||
}
|
||||
if err := iprot.ReadMapEnd(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *CommonException) Write(ctx context.Context, oprot thrift.TProtocol) (err error) {
|
||||
var fieldId int16
|
||||
if err = oprot.WriteStructBegin(ctx, "CommonException"); err != nil {
|
||||
goto WriteStructBeginError
|
||||
}
|
||||
if p != nil {
|
||||
if err = p.writeField1(ctx, oprot); err != nil {
|
||||
fieldId = 1
|
||||
goto WriteFieldError
|
||||
}
|
||||
if err = p.writeField2(ctx, oprot); err != nil {
|
||||
fieldId = 2
|
||||
goto WriteFieldError
|
||||
}
|
||||
if err = p.writeField3(ctx, oprot); err != nil {
|
||||
fieldId = 3
|
||||
goto WriteFieldError
|
||||
}
|
||||
|
||||
if err = p._unknownFields.Write(ctx, oprot); err != nil {
|
||||
goto UnknownFieldsWriteError
|
||||
}
|
||||
}
|
||||
if err = oprot.WriteFieldStop(ctx); err != nil {
|
||||
goto WriteFieldStopError
|
||||
}
|
||||
if err = oprot.WriteStructEnd(ctx); err != nil {
|
||||
goto WriteStructEndError
|
||||
}
|
||||
return nil
|
||||
WriteStructBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
|
||||
WriteFieldError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
|
||||
WriteFieldStopError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
|
||||
WriteStructEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
|
||||
UnknownFieldsWriteError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write unknown fields error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *CommonException) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin(ctx, "code", thrift.I32, 1); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteI32(ctx, p.Code); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(ctx); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *CommonException) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin(ctx, "message", thrift.STRING, 2); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteString(ctx, p.Message); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(ctx); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *CommonException) writeField3(ctx context.Context, oprot thrift.TProtocol) (err error) {
|
||||
if p.IsSetMetadata() {
|
||||
if err = oprot.WriteFieldBegin(ctx, "metadata", thrift.MAP, 3); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteMapBegin(ctx, thrift.STRING, thrift.STRING, len(p.Metadata)); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range p.Metadata {
|
||||
|
||||
if err := oprot.WriteString(ctx, k); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := oprot.WriteString(ctx, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := oprot.WriteMapEnd(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(ctx); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *CommonException) String() string {
|
||||
if p == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return fmt.Sprintf("CommonException(%+v)", *p)
|
||||
}
|
||||
func (p *CommonException) Error() string {
|
||||
return p.String()
|
||||
}
|
||||
5
pkg/gen/common/service.go
Normal file
5
pkg/gen/common/service.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Code generated by thriftgo (0.2.1). DO NOT EDIT.
|
||||
|
||||
package common
|
||||
|
||||
import ()
|
||||
90
pkg/gormlog/gormlog.go
Normal file
90
pkg/gormlog/gormlog.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package gormlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/utils"
|
||||
)
|
||||
|
||||
type GormLog struct {
|
||||
GormLogParams
|
||||
sug *zap.SugaredLogger
|
||||
}
|
||||
|
||||
var _ logger.Interface = (*GormLog)(nil)
|
||||
|
||||
type GormLogParams struct {
|
||||
LogLevel logger.LogLevel
|
||||
SlowThreshold time.Duration
|
||||
IgnoreRecordNotFoundError bool
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
func New(params GormLogParams) *GormLog {
|
||||
return &GormLog{
|
||||
GormLogParams: params,
|
||||
sug: params.Logger.Sugar().Named("gormlog"),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GormLog) LogMode(level logger.LogLevel) logger.Interface {
|
||||
var params = l.GormLogParams
|
||||
params.LogLevel = level
|
||||
return New(params)
|
||||
}
|
||||
|
||||
func (l *GormLog) Info(ctx context.Context, msg string, data ...any) {
|
||||
if l.LogLevel >= logger.Info {
|
||||
l.sug.Info(append([]any{msg}, data...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GormLog) Warn(ctx context.Context, msg string, data ...any) {
|
||||
if l.LogLevel >= logger.Warn {
|
||||
l.sug.Warn(append([]any{msg}, data...)...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (l *GormLog) Error(ctx context.Context, msg string, data ...any) {
|
||||
if l.LogLevel >= logger.Error {
|
||||
l.sug.Error(append([]any{msg}, data...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GormLog) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
||||
if l.LogLevel <= logger.Silent {
|
||||
return
|
||||
}
|
||||
|
||||
elapsed := time.Since(begin)
|
||||
switch {
|
||||
case err != nil && l.LogLevel >= logger.Error && (!errors.Is(err, logger.ErrRecordNotFound) || !l.IgnoreRecordNotFoundError):
|
||||
sql, rows := fc()
|
||||
if rows != -1 {
|
||||
l.sug.Errorf("trace %v %v %v %s %s", utils.FileWithLineNum(), err, elapsed.Milliseconds(), "-", sql)
|
||||
} else {
|
||||
l.sug.Errorf("trace %v %v %v %s %s", utils.FileWithLineNum(), err, elapsed.Milliseconds(), rows, sql)
|
||||
}
|
||||
case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= logger.Warn:
|
||||
sql, rows := fc()
|
||||
slowLog := fmt.Sprintf("SLOW SQL >= %v", l.SlowThreshold)
|
||||
if rows == -1 {
|
||||
l.sug.Warnf("trace %v %v %v %s %s", utils.FileWithLineNum(), slowLog, elapsed.Milliseconds(), "-", sql)
|
||||
} else {
|
||||
l.sug.Warnf("trace %v %v %v %s %s", utils.FileWithLineNum(), slowLog, elapsed.Milliseconds(), rows, sql)
|
||||
}
|
||||
case l.LogLevel == logger.Info:
|
||||
sql, rows := fc()
|
||||
if rows != -1 {
|
||||
l.sug.Infof("trace %v %v %s %s", utils.FileWithLineNum(), elapsed.Milliseconds(), "-", sql)
|
||||
} else {
|
||||
l.sug.Infof("trace %v %v %s %s", utils.FileWithLineNum(), elapsed.Milliseconds(), rows, sql)
|
||||
}
|
||||
}
|
||||
}
|
||||
29266
public/assets/index.0c1f2447.js
Normal file
29266
public/assets/index.0c1f2447.js
Normal file
File diff suppressed because it is too large
Load diff
12760
public/assets/index.241d8ed5.js
Normal file
12760
public/assets/index.241d8ed5.js
Normal file
File diff suppressed because one or more lines are too long
3683
public/assets/index.d6f403e9.css
Normal file
3683
public/assets/index.d6f403e9.css
Normal file
File diff suppressed because one or more lines are too long
15
public/index.html
Normal file
15
public/index.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<!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>Vite + Vue + TS</title>
|
||||
<script type="module" crossorigin src="/assets/index.241d8ed5.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.d6f403e9.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
14
public/manifest.json
Normal file
14
public/manifest.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"index.html": {
|
||||
"file": "assets/index.241d8ed5.js",
|
||||
"src": "index.html",
|
||||
"isEntry": true,
|
||||
"css": [
|
||||
"assets/index.d6f403e9.css"
|
||||
]
|
||||
},
|
||||
"index.css": {
|
||||
"file": "assets/index.d6f403e9.css",
|
||||
"src": "index.css"
|
||||
}
|
||||
}
|
||||
6
public/public.go
Normal file
6
public/public.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package public
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *
|
||||
var FS embed.FS
|
||||
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
8
tools.go
Normal file
8
tools.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package wpw
|
||||
|
||||
import (
|
||||
_ "github.com/ii64/thrift-idl-builder"
|
||||
)
|
||||
Loading…
Reference in a new issue