feat: add category option for site

This commit is contained in:
Jacky 2024-10-25 10:00:50 +08:00
parent 7ad5cac3b8
commit 207f80f858
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
16 changed files with 1452 additions and 508 deletions

View file

@ -69,7 +69,7 @@ func GetSite(c *gin.Context) {
c.JSON(http.StatusOK, Site{ c.JSON(http.StatusOK, Site{
ModifiedAt: file.ModTime(), ModifiedAt: file.ModTime(),
Advanced: site.Advanced, Site: site,
Enabled: enabled, Enabled: enabled,
Name: name, Name: name,
Config: string(origContent), Config: string(origContent),
@ -102,7 +102,7 @@ func GetSite(c *gin.Context) {
c.JSON(http.StatusOK, Site{ c.JSON(http.StatusOK, Site{
ModifiedAt: file.ModTime(), ModifiedAt: file.ModTime(),
Advanced: site.Advanced, Site: site,
Enabled: enabled, Enabled: enabled,
Name: name, Name: name,
Config: nginxConfig.FmtCode(), Config: nginxConfig.FmtCode(),
@ -125,9 +125,10 @@ func SaveSite(c *gin.Context) {
} }
var json struct { var json struct {
Name string `json:"name" binding:"required"` Name string `json:"name" binding:"required"`
Content string `json:"content" binding:"required"` Content string `json:"content" binding:"required"`
Overwrite bool `json:"overwrite"` SiteCategoryID uint64 `json:"site_category_id"`
Overwrite bool `json:"overwrite"`
} }
if !api.BindAndValid(c, &json) { if !api.BindAndValid(c, &json) {
@ -149,11 +150,18 @@ func SaveSite(c *gin.Context) {
return return
} }
enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name) enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name)
s := query.Site
_, err = s.Where(s.Path.Eq(path)).Update(s.SiteCategoryID, json.SiteCategoryID)
if err != nil {
api.ErrHandler(c, err)
return
}
// rename the config file if needed // rename the config file if needed
if name != json.Name { if name != json.Name {
newPath := nginx.GetConfPath("sites-available", json.Name) newPath := nginx.GetConfPath("sites-available", json.Name)
s := query.Site _, _ = s.Where(s.Path.Eq(path)).Update(s.Path, newPath)
_, err = s.Where(s.Path.Eq(path)).Update(s.Path, newPath)
// check if dst file exists, do not rename // check if dst file exists, do not rename
if helper.FileExists(newPath) { if helper.FileExists(newPath) {

View file

@ -3,15 +3,16 @@ package sites
import ( import (
"github.com/0xJacky/Nginx-UI/internal/cert" "github.com/0xJacky/Nginx-UI/internal/cert"
"github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/model"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"time" "time"
) )
type Site struct { type Site struct {
ModifiedAt time.Time `json:"modified_at"` *model.Site
Advanced bool `json:"advanced"`
Enabled bool `json:"enabled"`
Name string `json:"name"` Name string `json:"name"`
ModifiedAt time.Time `json:"modified_at"`
Enabled bool `json:"enabled"`
Config string `json:"config"` Config string `json:"config"`
AutoCert bool `json:"auto_cert"` AutoCert bool `json:"auto_cert"`
ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"` ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`

View file

@ -39,6 +39,7 @@
"reconnecting-websocket": "^4.4.0", "reconnecting-websocket": "^4.4.0",
"sortablejs": "^1.15.3", "sortablejs": "^1.15.3",
"universal-cookie": "^7.2.1", "universal-cookie": "^7.2.1",
"unocss": "^0.63.6",
"vite-plugin-build-id": "0.4.2", "vite-plugin-build-id": "0.4.2",
"vue": "^3.5.12", "vue": "^3.5.12",
"vue-dompurify-html": "^5.1.0", "vue-dompurify-html": "^5.1.0",
@ -51,6 +52,12 @@
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^3.8.0", "@antfu/eslint-config": "^3.8.0",
"@iconify-json/fa": "1.1.5",
"@iconify-json/tabler": "1.1.95",
"@iconify/tools": "3.0.5",
"@iconify/types": "^2.0.0",
"@iconify/utils": "^2.1.33",
"@iconify/vue": "4.1.1",
"@simplewebauthn/types": "^11.0.0", "@simplewebauthn/types": "^11.0.0",
"@types/lodash": "^4.17.12", "@types/lodash": "^4.17.12",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",

1485
app/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View file

@ -1,6 +1,7 @@
import type { CertificateInfo } from '@/api/cert' import type { CertificateInfo } from '@/api/cert'
import type { NgxConfig } from '@/api/ngx' import type { NgxConfig } from '@/api/ngx'
import type { ChatComplicationMessage } from '@/api/openai' import type { ChatComplicationMessage } from '@/api/openai'
import type { SiteCategory } from '@/api/site_category'
import type { PrivateKeyType } from '@/constants' import type { PrivateKeyType } from '@/constants'
import Curd from '@/api/curd' import Curd from '@/api/curd'
import http from '@/lib/http' import http from '@/lib/http'
@ -16,6 +17,8 @@ export interface Site {
chatgpt_messages: ChatComplicationMessage[] chatgpt_messages: ChatComplicationMessage[]
tokenized?: NgxConfig tokenized?: NgxConfig
cert_info?: Record<number, CertificateInfo[]> cert_info?: Record<number, CertificateInfo[]>
site_category_id: number
site_category?: SiteCategory
} }
export interface AutoCertRequest { export interface AutoCertRequest {

View file

@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type Curd from '@/api/curd' import type Curd from '@/api/curd'
import type { Column } from '@/components/StdDesign/types' import type { Column } from '@/components/StdDesign/types'
import type { Ref } from 'vue'
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue' import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
import { watchOnce } from '@vueuse/core'
import _ from 'lodash' import _ from 'lodash'
const props = defineProps<{ const props = defineProps<{
placeholder?: string
label?: string label?: string
selectedKey: number | number[] | undefined | null
selectionType: 'radio' | 'checkbox' selectionType: 'radio' | 'checkbox'
recordValueIndex: string // to index the value of the record recordValueIndex: string // to index the value of the record
// eslint-disable-next-line ts/no-explicit-any // eslint-disable-next-line ts/no-explicit-any
@ -24,9 +24,19 @@ const props = defineProps<{
disabled?: boolean disabled?: boolean
// eslint-disable-next-line ts/no-explicit-any // eslint-disable-next-line ts/no-explicit-any
valueApi?: Curd<any> valueApi?: Curd<any>
// eslint-disable-next-line ts/no-explicit-any
getCheckboxProps?: (record: any) => any
hideInputContainer?: boolean
}>() }>()
const emit = defineEmits(['update:selectedKey']) const selectedKey = defineModel<number | number[] | undefined | null | string | string[]>('selectedKey')
onMounted(() => {
if (!selectedKey.value)
watchOnce(selectedKey, _init)
else
_init()
})
const getParams = computed(() => { const getParams = computed(() => {
return props.getParams return props.getParams
@ -34,19 +44,23 @@ const getParams = computed(() => {
const visible = ref(false) const visible = ref(false)
// eslint-disable-next-line ts/no-explicit-any // eslint-disable-next-line ts/no-explicit-any
const M_values = ref([]) as any const M_values = ref([]) as Ref<any[]>
const init = _.debounce(_init, 500, { const ComputedMValue = computed(() => {
leading: true, return M_values.value.filter(v => v && Object.keys(v).length > 0)
trailing: false,
})
onMounted(() => {
init()
}) })
// eslint-disable-next-line ts/no-explicit-any // eslint-disable-next-line ts/no-explicit-any
const records = ref([]) as Ref<any[]> const records = defineModel<any[]>('selectedRecords', {
default: () => [],
})
watch(() => props.value, () => {
if (props.selectionType === 'radio')
M_values.value = [props.value]
else if (typeof selectedKey.value === 'object')
M_values.value = props.value || []
})
async function _init() { async function _init() {
// valueApi is used to fetch items that are using itemKey as index value // valueApi is used to fetch items that are using itemKey as index value
@ -55,22 +69,22 @@ async function _init() {
M_values.value = [] M_values.value = []
if (props.selectionType === 'radio') { if (props.selectionType === 'radio') {
// M_values.value = [props.value] // not init value, we need to fetch them from api // M_values.value = [props.value]
if (!props.value && props.selectedKey) { // not init value, we need to fetch them from api
api.get(props.selectedKey, props.getParams).then(r => { if (!props.value && selectedKey.value && selectedKey.value !== '0') {
api.get(selectedKey.value, props.getParams).then(r => {
M_values.value = [r] M_values.value = [r]
records.value = [r] records.value = [r]
}) })
} }
} }
else if (typeof props.selectedKey === 'object') { else if (typeof selectedKey.value === 'object') {
M_values.value = props.value || [] // M_values.value = props.value || []
// not init value, we need to fetch them from api // not init value, we need to fetch them from api
if (!props.value && (props.selectedKey?.length || 0) > 0) { if (!props.value && (selectedKey.value?.length || 0) > 0) {
api.get_list({ api.get_list({
...props.getParams, ...props.getParams,
id: props.selectedKey, id: selectedKey.value,
}).then(r => { }).then(r => {
M_values.value = r.data M_values.value = r.data
records.value = r.data records.value = r.data
@ -85,11 +99,22 @@ function show() {
} }
const selectedKeyBuffer = ref() const selectedKeyBuffer = ref()
// eslint-disable-next-line ts/no-explicit-any
const selectedBuffer: Ref<any[]> = ref([])
if (props.selectionType === 'radio') watch(selectedKey, () => {
selectedKeyBuffer.value = [props.selectedKey] selectedKeyBuffer.value = _.clone(selectedKey.value)
else })
selectedKeyBuffer.value = props.selectedKey
watch(records, v => {
selectedBuffer.value = [...v]
M_values.value = [...v]
})
onMounted(() => {
selectedKeyBuffer.value = _.clone(selectedKey.value)
selectedBuffer.value = _.clone(records.value)
})
const computedSelectedKeys = computed({ const computedSelectedKeys = computed({
get() { get() {
@ -103,76 +128,80 @@ const computedSelectedKeys = computed({
}, },
}) })
onMounted(() => {
if (props.selectedKey === undefined || props.selectedKey === null) {
if (props.selectionType === 'radio')
emit('update:selectedKey', '')
else
emit('update:selectedKey', [])
}
})
async function ok() { async function ok() {
visible.value = false visible.value = false
emit('update:selectedKey', selectedKeyBuffer.value) selectedKey.value = selectedKeyBuffer.value
records.value = selectedBuffer.value
await nextTick()
M_values.value = _.clone(records.value) M_values.value = _.clone(records.value)
} }
watchEffect(() => {
init()
})
// function clear() { // function clear() {
// M_values.value = [] // M_values.value = []
// emit('update:selectedKey', '') // emit('update:selectedKey', '')
// } // }
defineExpose({ show })
</script> </script>
<template> <template>
<div class="std-selector-container"> <div>
<div <div
class="std-selector" v-if="!hideInputContainer"
@click="show" class="std-selector-container"
> >
<div class="chips-container"> <div
<ATag class="std-selector"
v-for="(chipText, index) in M_values" @click="show"
:key="index"
class="mr-1"
color="orange"
:bordered="false"
@click="show"
>
{{ chipText?.[recordValueIndex] }}
</ATag>
</div>
<AModal
:mask="false"
:open="visible"
:cancel-text="$gettext('Cancel')"
:ok-text="$gettext('Ok')"
:title="$gettext('Selector')"
:width="800"
destroy-on-close
@cancel="visible = false"
@ok="ok"
> >
{{ description }} <div class="chips-container">
<StdTable <div v-if="props.recordValueIndex">
v-model:selected-row-keys="computedSelectedKeys" <ATag
v-model:selected-rows="records" v-for="(chipText, index) in ComputedMValue"
:api="api" :key="index"
:columns="columns" class="mr-1"
:disable-search="disableSearch" color="orange"
pithy :bordered="false"
:row-key="itemKey" @click="show"
:get-params="getParams" >
:selection-type="selectionType" {{ chipText?.[recordValueIndex] }}
disable-query-params </ATag>
/> </div>
</AModal> <div
v-else
class="text-gray-400"
>
{{ placeholder }}
</div>
</div>
</div>
</div> </div>
<AModal
:mask="false"
:open="visible"
:cancel-text="$gettext('Cancel')"
:ok-text="$gettext('Ok')"
:title="$gettext('Selector')"
:width="800"
destroy-on-close
@cancel="visible = false"
@ok="ok"
>
{{ description }}
<StdTable
v-model:selected-row-keys="computedSelectedKeys"
v-model:selected-rows="selectedBuffer"
:api
:columns
:disable-search
:row-key="itemKey"
:get-params
:selection-type
:get-checkbox-props
pithy
disable-query-params
/>
</AModal>
</div> </div>
</template> </template>
@ -180,7 +209,7 @@ watchEffect(() => {
.std-selector-container { .std-selector-container {
min-height: 39.9px; min-height: 39.9px;
display: flex; display: flex;
align-items: flex-start; align-items: self-start;
.std-selector { .std-selector {
overflow-y: auto; overflow-y: auto;
@ -195,7 +224,7 @@ watchEffect(() => {
line-height: 1.5; line-height: 1.5;
background-image: none; background-image: none;
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 4px; border-radius: 6px;
transition: all 0.3s; transition: all 0.3s;
//margin: 0 10px 0 0; //margin: 0 10px 0 0;
cursor: pointer; cursor: pointer;
@ -203,9 +232,10 @@ watchEffect(() => {
} }
} }
.chips-container { .dark {
span { .std-selector {
margin: 2px; border: 1px solid #424242;
background-color: #141414;
} }
} }
</style> </style>

View file

@ -7,7 +7,7 @@ import VueDOMPurifyHTML from 'vue-dompurify-html'
import App from './App.vue' import App from './App.vue'
import gettext from './gettext' import gettext from './gettext'
import router from './routes' import router from './routes'
import './style.css' import 'virtual:uno.css'
const pinia = createPinia() const pinia = createPinia()

View file

@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -40,7 +40,7 @@ const saving = ref(false)
const filename = ref('') const filename = ref('')
const parse_error_status = ref(false) const parse_error_status = ref(false)
const parse_error_message = ref('') const parse_error_message = ref('')
const data = ref({}) const data = ref({}) as Ref<Site>
init() init()
@ -134,6 +134,7 @@ async function save() {
name: filename.value || name.value, name: filename.value || name.value,
content: configText.value, content: configText.value,
overwrite: true, overwrite: true,
site_category_id: data.value.site_category_id,
}).then(r => { }).then(r => {
handle_response(r) handle_response(r)
router.push({ router.push({

View file

@ -4,10 +4,13 @@ import type { ChatComplicationMessage } from '@/api/openai'
import type { CheckedType } from '@/types' import type { CheckedType } from '@/types'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import domain from '@/api/domain' import domain from '@/api/domain'
import site_category from '@/api/site_category'
import ChatGPT from '@/components/ChatGPT/ChatGPT.vue' import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
import StdSelector from '@/components/StdDesign/StdDataEntry/components/StdSelector.vue'
import { formatDateTime } from '@/lib/helper' import { formatDateTime } from '@/lib/helper'
import { useSettingsStore } from '@/pinia' import { useSettingsStore } from '@/pinia'
import Deploy from '@/views/site/components/Deploy.vue' import Deploy from '@/views/site/components/Deploy.vue'
import siteCategoryColumns from '@/views/site/site_category/columns'
import { message, Modal } from 'ant-design-vue' import { message, Modal } from 'ant-design-vue'
const settings = useSettingsStore() const settings = useSettingsStore()
@ -73,18 +76,29 @@ function on_change_enabled(checked: CheckedType) {
key="1" key="1"
:header="$gettext('Basic')" :header="$gettext('Basic')"
> >
<AFormItem :label="$gettext('Enabled')"> <AForm layout="vertical">
<ASwitch <AFormItem :label="$gettext('Enabled')">
:checked="enabled" <ASwitch
@change="on_change_enabled" :checked="enabled"
/> @change="on_change_enabled"
</AFormItem> />
<AFormItem :label="$gettext('Name')"> </AFormItem>
<AInput v-model:value="filename" /> <AFormItem :label="$gettext('Name')">
</AFormItem> <AInput v-model:value="filename" />
<AFormItem :label="$gettext('Updated at')"> </AFormItem>
{{ formatDateTime(data.modified_at) }} <AFormItem :label="$gettext('Category')">
</AFormItem> <StdSelector
v-model:selected-key="data.site_category_id"
:api="site_category"
:columns="siteCategoryColumns"
record-value-index="name"
selection-type="radio"
/>
</AFormItem>
<AFormItem :label="$gettext('Updated at')">
{{ formatDateTime(data.modified_at) }}
</AFormItem>
</AForm>
</ACollapsePanel> </ACollapsePanel>
<ACollapsePanel <ACollapsePanel
v-if="!settings.is_remote" v-if="!settings.is_remote"

View file

@ -1,14 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./index.html',
'./src/**/*.{vue,js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
corePlugins: {
preflight: false,
},
}

66
app/uno.config.ts Normal file
View file

@ -0,0 +1,66 @@
// uno.config.ts
import {
defineConfig,
presetAttributify,
presetIcons,
presetTypography,
presetUno,
presetWebFonts,
transformerDirectives,
transformerVariantGroup,
} from 'unocss'
export default defineConfig({
shortcuts: [],
rules: [],
variants: [
// 使用工具函数
matcher => {
if (!matcher.endsWith('!'))
return matcher
return {
matcher: matcher.slice(0, -1),
selector: s => `${s}!important`,
}
},
],
theme: {
colors: {
// ...
},
},
presets: [
presetUno(),
presetAttributify(),
presetIcons({
collections: {
tabler: () => import('@iconify-json/tabler/icons.json').then(i => i.default),
},
extraProperties: {
'display': 'inline-block',
'height': '1.2em',
'width': '1.2em',
'vertical-align': 'text-bottom',
},
}),
presetTypography(),
presetWebFonts(),
],
transformers: [
transformerDirectives(),
transformerVariantGroup(),
],
content: {
pipeline: {
include: [
// default
/\.(vue|[jt]sx|ts)($|\?)/,
// 参考https://unocss.dev/guide/extracting#extracting-from-build-tools-pipeline
],
// exclude files
// exclude: []
},
},
})

View file

@ -1,10 +1,10 @@
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx' import vueJsx from '@vitejs/plugin-vue-jsx'
import UnoCSS from 'unocss/vite'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import DefineOptions from 'unplugin-vue-define-options/vite' import DefineOptions from 'unplugin-vue-define-options/vite'
import { defineConfig, loadEnv } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vitePluginBuildId from 'vite-plugin-build-id' import vitePluginBuildId from 'vite-plugin-build-id'
@ -34,9 +34,9 @@ export default defineConfig(({ mode }) => {
plugins: [ plugins: [
vue(), vue(),
vueJsx(), vueJsx(),
vitePluginBuildId(), vitePluginBuildId(),
svgLoader(), svgLoader(),
UnoCSS(),
Components({ Components({
resolvers: [AntDesignVueResolver({ importStyle: false })], resolvers: [AntDesignVueResolver({ importStyle: false })],
directoryAsNamespace: true, directoryAsNamespace: true,

View file

@ -2,6 +2,8 @@ package model
type Site struct { type Site struct {
Model Model
Path string `json:"path"` Path string `json:"path"`
Advanced bool `json:"advanced"` Advanced bool `json:"advanced"`
SiteCategoryID uint64 `json:"site_category_id"`
SiteCategory *SiteCategory `json:"site_category,omitempty"`
} }

View file

@ -34,6 +34,12 @@ func newSite(db *gorm.DB, opts ...gen.DOOption) site {
_site.DeletedAt = field.NewField(tableName, "deleted_at") _site.DeletedAt = field.NewField(tableName, "deleted_at")
_site.Path = field.NewString(tableName, "path") _site.Path = field.NewString(tableName, "path")
_site.Advanced = field.NewBool(tableName, "advanced") _site.Advanced = field.NewBool(tableName, "advanced")
_site.SiteCategoryID = field.NewUint64(tableName, "site_category_id")
_site.SiteCategory = siteBelongsToSiteCategory{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("SiteCategory", "model.SiteCategory"),
}
_site.fillFieldMap() _site.fillFieldMap()
@ -43,13 +49,15 @@ func newSite(db *gorm.DB, opts ...gen.DOOption) site {
type site struct { type site struct {
siteDo siteDo
ALL field.Asterisk ALL field.Asterisk
ID field.Uint64 ID field.Uint64
CreatedAt field.Time CreatedAt field.Time
UpdatedAt field.Time UpdatedAt field.Time
DeletedAt field.Field DeletedAt field.Field
Path field.String Path field.String
Advanced field.Bool Advanced field.Bool
SiteCategoryID field.Uint64
SiteCategory siteBelongsToSiteCategory
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@ -72,6 +80,7 @@ func (s *site) updateTableName(table string) *site {
s.DeletedAt = field.NewField(table, "deleted_at") s.DeletedAt = field.NewField(table, "deleted_at")
s.Path = field.NewString(table, "path") s.Path = field.NewString(table, "path")
s.Advanced = field.NewBool(table, "advanced") s.Advanced = field.NewBool(table, "advanced")
s.SiteCategoryID = field.NewUint64(table, "site_category_id")
s.fillFieldMap() s.fillFieldMap()
@ -88,13 +97,15 @@ func (s *site) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (s *site) fillFieldMap() { func (s *site) fillFieldMap() {
s.fieldMap = make(map[string]field.Expr, 6) s.fieldMap = make(map[string]field.Expr, 8)
s.fieldMap["id"] = s.ID s.fieldMap["id"] = s.ID
s.fieldMap["created_at"] = s.CreatedAt s.fieldMap["created_at"] = s.CreatedAt
s.fieldMap["updated_at"] = s.UpdatedAt s.fieldMap["updated_at"] = s.UpdatedAt
s.fieldMap["deleted_at"] = s.DeletedAt s.fieldMap["deleted_at"] = s.DeletedAt
s.fieldMap["path"] = s.Path s.fieldMap["path"] = s.Path
s.fieldMap["advanced"] = s.Advanced s.fieldMap["advanced"] = s.Advanced
s.fieldMap["site_category_id"] = s.SiteCategoryID
} }
func (s site) clone(db *gorm.DB) site { func (s site) clone(db *gorm.DB) site {
@ -107,6 +118,77 @@ func (s site) replaceDB(db *gorm.DB) site {
return s return s
} }
type siteBelongsToSiteCategory struct {
db *gorm.DB
field.RelationField
}
func (a siteBelongsToSiteCategory) Where(conds ...field.Expr) *siteBelongsToSiteCategory {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a siteBelongsToSiteCategory) WithContext(ctx context.Context) *siteBelongsToSiteCategory {
a.db = a.db.WithContext(ctx)
return &a
}
func (a siteBelongsToSiteCategory) Session(session *gorm.Session) *siteBelongsToSiteCategory {
a.db = a.db.Session(session)
return &a
}
func (a siteBelongsToSiteCategory) Model(m *model.Site) *siteBelongsToSiteCategoryTx {
return &siteBelongsToSiteCategoryTx{a.db.Model(m).Association(a.Name())}
}
type siteBelongsToSiteCategoryTx struct{ tx *gorm.Association }
func (a siteBelongsToSiteCategoryTx) Find() (result *model.SiteCategory, err error) {
return result, a.tx.Find(&result)
}
func (a siteBelongsToSiteCategoryTx) Append(values ...*model.SiteCategory) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a siteBelongsToSiteCategoryTx) Replace(values ...*model.SiteCategory) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a siteBelongsToSiteCategoryTx) Delete(values ...*model.SiteCategory) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a siteBelongsToSiteCategoryTx) Clear() error {
return a.tx.Clear()
}
func (a siteBelongsToSiteCategoryTx) Count() int64 {
return a.tx.Count()
}
type siteDo struct{ gen.DO } type siteDo struct{ gen.DO }
// FirstByID Where("id=@id") // FirstByID Where("id=@id")