mirror of https://github.com/sunface/rust-course
parent
4feea1b987
commit
aeaf7aacd2
@ -0,0 +1,158 @@
|
||||
import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, Table, Thead, Tr, Th, Tbody, Td, IconButton, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, NumberInput, NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper } from "@chakra-ui/react"
|
||||
import Card from "components/card"
|
||||
import PageContainer from "layouts/page-container"
|
||||
import Sidebar from "layouts/sidebar/sidebar"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { adminLinks, settingLinks } from "src/data/links"
|
||||
import { requestApi } from "utils/axios/request"
|
||||
import { config } from "configs/config"
|
||||
import { getSvgIcon } from "components/svg-icon"
|
||||
import { Navbar, NavbarType } from "src/types/user"
|
||||
import { cloneDeep } from "lodash"
|
||||
import { IDType } from "src/types/id"
|
||||
import { Story } from "src/types/story"
|
||||
import PageContainer1 from "layouts/page-container1"
|
||||
|
||||
const UserNavbarPage = () => {
|
||||
const [navbars, setNavbars]:[Navbar[],any] = useState([])
|
||||
const [series, setSeries]: [Story[], any] = useState([])
|
||||
const [currentNavbar, setCurrentNavbar]: [Navbar, any] = useState(null)
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const toast = useToast()
|
||||
useEffect(() => {
|
||||
getNavbars()
|
||||
getSeries()
|
||||
}, [])
|
||||
|
||||
const getNavbars = async () => {
|
||||
const res = await requestApi.get("/navbars")
|
||||
setNavbars(res.data)
|
||||
}
|
||||
|
||||
const getSeries = async () => {
|
||||
const res = await requestApi.get(`/story/posts/editor?type=${IDType.Series}`)
|
||||
setSeries(res.data)
|
||||
}
|
||||
|
||||
const submitNavbar = async () => {
|
||||
if (!currentNavbar.label || !currentNavbar.value) {
|
||||
toast({
|
||||
description: "值不能为空",
|
||||
status: "error",
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (currentNavbar.label.length > config.user.navbarMaxLen) {
|
||||
toast({
|
||||
description: `Label长度不能超过${config.user.navbarMaxLen}`,
|
||||
status: "error",
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
await requestApi.post(`/navbar`, currentNavbar)
|
||||
setCurrentNavbar(null)
|
||||
onClose()
|
||||
getNavbars()
|
||||
}
|
||||
|
||||
const onAddNavbar = () => {
|
||||
setCurrentNavbar({ weight: 0, type: NavbarType.Link, label: "", value: "" })
|
||||
onOpen()
|
||||
}
|
||||
|
||||
const onEditNavbar = nav => {
|
||||
setCurrentNavbar(nav)
|
||||
onOpen()
|
||||
}
|
||||
|
||||
const onNavbarChange = () => {
|
||||
const nv = cloneDeep(currentNavbar)
|
||||
setCurrentNavbar(nv)
|
||||
}
|
||||
|
||||
const onDeleteNavbar = async id => {
|
||||
requestApi.delete(`/navbar/${id}`)
|
||||
setTimeout( () => getNavbars(),300)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContainer1>
|
||||
<Box display="flex">
|
||||
<Sidebar routes={adminLinks} width={["120px", "120px", "250px", "250px"]} height="fit-content" title="博客设置" />
|
||||
<Card ml="4" width="100%">
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<Heading size="sm">菜单设置</Heading>
|
||||
<Button colorScheme="teal" size="sm" onClick={onAddNavbar} _focus={null}>新建菜单项</Button>
|
||||
</Flex>
|
||||
<Table variant="simple" mt="4">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Label</Th>
|
||||
<Th>Value</Th>
|
||||
<Th>Weight</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{
|
||||
navbars.map((nv,i) => <Tr key={i}>
|
||||
<Td>{nv.label}</Td>
|
||||
<Td>{nv.value}</Td>
|
||||
<Td>{nv.weight}</Td>
|
||||
<Td>
|
||||
<IconButton aria-label="edit navbar" variant="ghost" icon={getSvgIcon('edit', ".95rem")} onClick={() => onEditNavbar(nv)}/>
|
||||
<IconButton aria-label="delete navbar" variant="ghost" icon={getSvgIcon('close', "1rem")} onClick={() => onDeleteNavbar(nv.id)} />
|
||||
</Td>
|
||||
</Tr>)
|
||||
}
|
||||
|
||||
</Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
</Box>
|
||||
</PageContainer1>
|
||||
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
{currentNavbar && <ModalContent>
|
||||
<ModalHeader>{currentNavbar.label ? "编辑菜单项" : "新建菜单项"}</ModalHeader>
|
||||
<ModalBody mb="2">
|
||||
<VStack spacing="4" alignItems="left">
|
||||
<HStack spacing="4">
|
||||
<Heading size="xs">Label</Heading>
|
||||
<Input value={currentNavbar.label} _focus={null} variant="flushed" onChange={e => { currentNavbar.label = e.currentTarget.value; onNavbarChange() }}></Input>
|
||||
</HStack>
|
||||
|
||||
<HStack spacing="4">
|
||||
<Heading size="xs">Value</Heading>
|
||||
<Input value={currentNavbar.value} _focus={null} variant="flushed" onChange={e => { currentNavbar.value = e.currentTarget.value; onNavbarChange() }} placeholder="enter a url, e.g /search"/>
|
||||
</HStack>
|
||||
|
||||
<HStack spacing="4">
|
||||
<Heading size="xs">Weight</Heading>
|
||||
<NumberInput min={0} max={10} value={currentNavbar.weight} variant="flushed" onChange={e => { currentNavbar.weight = parseInt(e); onNavbarChange() }}>
|
||||
<NumberInputField _focus={null} />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
</HStack>
|
||||
</VStack>
|
||||
<Button colorScheme="teal" variant="outline" mt="6" onClick={submitNavbar}>提交</Button>
|
||||
</ModalBody>
|
||||
</ModalContent>}
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default UserNavbarPage
|
||||
|
@ -0,0 +1,163 @@
|
||||
import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, Table, Thead, Tr, Th, Tbody, Td, IconButton, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, NumberInput, NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper, useColorModeValue, StackDivider } from "@chakra-ui/react"
|
||||
import Card from "components/card"
|
||||
import PageContainer from "layouts/page-container"
|
||||
import Sidebar from "layouts/sidebar/sidebar"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { settingLinks } from "src/data/links"
|
||||
import { requestApi } from "utils/axios/request"
|
||||
import { Org } from "src/types/org"
|
||||
import { Field, Form, Formik } from "formik"
|
||||
import { config } from "configs/config"
|
||||
import { isUsernameChar, usernameInvalidTips } from "utils/user"
|
||||
import { isAdmin } from "utils/role"
|
||||
import userCustomTheme from "theme/user-custom"
|
||||
import { useRouter } from "next/router"
|
||||
import Link from "next/link"
|
||||
|
||||
const UserOrgsPage = () => {
|
||||
const [orgs, setOrgs]:[Org[],any] = useState([])
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const router = useRouter()
|
||||
const stackBorderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
|
||||
|
||||
useEffect(() => {
|
||||
getOrgs()
|
||||
}, [])
|
||||
|
||||
const getOrgs = async () => {
|
||||
const res = await requestApi.get("/org/byUserID/0")
|
||||
setOrgs(res.data)
|
||||
}
|
||||
|
||||
|
||||
const createOrg = async (values:Org) => {
|
||||
await requestApi.post(`/org/create`, values)
|
||||
onClose()
|
||||
router.push(`/${values.username}`)
|
||||
}
|
||||
|
||||
const onCreateOrg = () => {
|
||||
onOpen()
|
||||
}
|
||||
|
||||
const validateUsername = async value => {
|
||||
let error
|
||||
if (!value?.trim()) {
|
||||
return "不能为空"
|
||||
}
|
||||
|
||||
if (value?.length > config.user.usernameMaxLen) {
|
||||
return `长度不能超过${config.user.usernameMaxLen}`
|
||||
}
|
||||
|
||||
for (const c of value) {
|
||||
if (!isUsernameChar(c)) {
|
||||
return usernameInvalidTips
|
||||
}
|
||||
}
|
||||
|
||||
const res = await requestApi.get(`/username/exist/${value}`)
|
||||
if (res.data) {
|
||||
return `The name '${value}' is already taken.`
|
||||
}
|
||||
return error
|
||||
}
|
||||
|
||||
function validateNickname(value) {
|
||||
let error
|
||||
if (!value?.trim()) {
|
||||
error = "不能为空"
|
||||
}
|
||||
|
||||
if (value?.length > config.user.usernameMaxLen) {
|
||||
error = `长度不能超过${config.user.usernameMaxLen}`
|
||||
}
|
||||
|
||||
return error
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContainer>
|
||||
<Box display="flex">
|
||||
<Sidebar routes={settingLinks} width={["120px", "120px", "250px", "250px"]} height="fit-content" title="博客设置" />
|
||||
<Card ml="4" width="100%">
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<Heading size="sm">组织管理</Heading>
|
||||
<Button colorScheme="teal" size="sm" onClick={onCreateOrg} _focus={null}>新建组织</Button>
|
||||
</Flex>
|
||||
|
||||
<VStack mt="3" divider={<StackDivider borderColor={stackBorderColor} />} alignItems="left">
|
||||
{
|
||||
orgs.map(o => <Flex key={o.id} justifyContent="space-between" alignItems="center" p="2">
|
||||
<Link href={`/${o.username}`}>
|
||||
<HStack cursor="pointer">
|
||||
<Image src={o.avatar} height="30px"/>
|
||||
<Heading size="sm" fontSize="1rem">{o.nickname}</Heading>
|
||||
<Text layerStyle="textSecondary">{isAdmin(o.role) ? 'admin' : 'member'}</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
|
||||
<Button variant="ghost">View</Button>
|
||||
</Flex>)
|
||||
}
|
||||
</VStack>
|
||||
|
||||
</Card>
|
||||
</Box>
|
||||
</PageContainer>
|
||||
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
{<ModalContent>
|
||||
<ModalHeader>新建组织</ModalHeader>
|
||||
<ModalBody mb="2">
|
||||
<Formik
|
||||
initialValues={{username: '',nickname:''} as Org}
|
||||
onSubmit={createOrg}
|
||||
>
|
||||
{(props) => (
|
||||
<Form>
|
||||
<VStack>
|
||||
<Field name="username" validate={validateUsername}>
|
||||
{({ field, form }) => (
|
||||
<FormControl isInvalid={form.errors.username && form.touched.username} >
|
||||
<FormLabel>Username</FormLabel>
|
||||
<Input {...field} placeholder="name" />
|
||||
<FormErrorMessage>{form.errors.username}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="nickname" validate={validateNickname}>
|
||||
{({ field, form }) => (
|
||||
<FormControl isInvalid={form.errors.nickname && form.touched.nickname} >
|
||||
<FormLabel>Nickname</FormLabel>
|
||||
<Input {...field} placeholder="name" />
|
||||
<FormErrorMessage>{form.errors.nickname}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
</VStack>
|
||||
<Box mt={6}>
|
||||
<Button
|
||||
colorScheme="teal"
|
||||
variant="outline"
|
||||
type="submit"
|
||||
_focus={null}
|
||||
>
|
||||
提交
|
||||
</Button>
|
||||
<Button variant="ghost" ml="4" _focus={null} onClick={onClose}>取消</Button>
|
||||
</Box>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</ModalBody>
|
||||
</ModalContent>}
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default UserOrgsPage
|
||||
|
@ -1,63 +0,0 @@
|
||||
import useCaretPosition from 'components/markdown-editor/position'
|
||||
import TestStyles from 'theme/caret.styles'
|
||||
import React, { useRef, useState, useEffect, Fragment } from 'react'
|
||||
import { render } from 'react-dom'
|
||||
|
||||
|
||||
const App = () => {
|
||||
const triggerRef = useRef(null)
|
||||
const [showTrigger, setShowTrigger] = useState(false)
|
||||
const {
|
||||
x: triggerX,
|
||||
y: triggerY,
|
||||
getPosition: getPositionTrigger,
|
||||
} = useCaretPosition(triggerRef)
|
||||
|
||||
const handleCustomUI = (e) => {
|
||||
const previousCharacter = e.target.value
|
||||
.charAt(triggerRef.current.selectionStart - 2)
|
||||
.trim()
|
||||
const character = e.target.value
|
||||
.charAt(triggerRef.current.selectionStart - 1)
|
||||
.trim()
|
||||
if (character === '@' && previousCharacter === '') {
|
||||
setShowTrigger(true)
|
||||
}
|
||||
if (character === '' && showTrigger) {
|
||||
setShowTrigger(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerRef.current) {
|
||||
getPositionTrigger(triggerRef)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<TestStyles />
|
||||
<section>
|
||||
<textarea
|
||||
ref={triggerRef}
|
||||
placeholder="Type the @ symbol to trigger UI"
|
||||
spellCheck="false"
|
||||
onKeyUp={handleCustomUI}
|
||||
onInput={() => getPositionTrigger(triggerRef)}
|
||||
/>
|
||||
<span
|
||||
className="marker marker--trigger"
|
||||
style={{
|
||||
display: showTrigger ? 'block' : 'none',
|
||||
//@ts-ignore
|
||||
'--y': triggerY,
|
||||
'--x': triggerX,
|
||||
}}>
|
||||
Triggered UI! <span role="img">😎</span>
|
||||
</span>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
@ -0,0 +1,45 @@
|
||||
// Copyright © 2019 NAME HERE <EMAIL ADDRESS>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/imdotdev/im.dev/server/internal/storage"
|
||||
"github.com/imdotdev/im.dev/server/pkg/config"
|
||||
"github.com/imdotdev/im.dev/server/pkg/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var resetNavbarCmd = &cobra.Command{
|
||||
Use: "resetNav",
|
||||
Short: "reset navbars to home,tags,search",
|
||||
Long: ``,
|
||||
// Uncomment the following line if your bare application
|
||||
// has an action associated with it:
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
config.Init("config.yaml")
|
||||
log.InitLogger(config.Data.Common.LogLevel)
|
||||
err := storage.ResetNavbars()
|
||||
if err != nil {
|
||||
log.RootLogger.Crit("reset navbars error", "error", err)
|
||||
} else {
|
||||
log.RootLogger.Info("reset navbars successfully")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(resetNavbarCmd)
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/imdotdev/im.dev/server/internal/org"
|
||||
"github.com/imdotdev/im.dev/server/internal/user"
|
||||
"github.com/imdotdev/im.dev/server/pkg/common"
|
||||
"github.com/imdotdev/im.dev/server/pkg/e"
|
||||
"github.com/imdotdev/im.dev/server/pkg/models"
|
||||
)
|
||||
|
||||
func CreateOrg(c *gin.Context) {
|
||||
o := &models.User{}
|
||||
c.Bind(&o)
|
||||
|
||||
exist, err := user.NameExist(o.Username)
|
||||
if err != nil {
|
||||
c.JSON(err.Status, common.RespError(err.Message))
|
||||
return
|
||||
}
|
||||
|
||||
if exist {
|
||||
c.JSON(http.StatusConflict, common.RespError(e.AlreadyExist))
|
||||
return
|
||||
}
|
||||
|
||||
u := user.CurrentUser(c)
|
||||
err = org.Create(o, u.ID)
|
||||
if err != nil {
|
||||
c.JSON(err.Status, common.RespError(err.Message))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, common.RespSuccess(nil))
|
||||
}
|
||||
|
||||
func UpdateOrg(c *gin.Context) {
|
||||
|
||||
}
|
||||
|
||||
func GetOrgByUserID(c *gin.Context) {
|
||||
userID := c.Param("userID")
|
||||
if userID == "0" {
|
||||
u := user.CurrentUser(c)
|
||||
if u == nil {
|
||||
c.JSON(http.StatusBadRequest, common.RespError(e.BadRequest))
|
||||
return
|
||||
}
|
||||
|
||||
userID = u.ID
|
||||
}
|
||||
|
||||
orgs, err := org.GetOrgByUserID(userID)
|
||||
if err != nil {
|
||||
c.JSON(err.Status, common.RespError(err.Message))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, common.RespSuccess(orgs))
|
||||
}
|
||||
|
||||
func GetOrgMembers(c *gin.Context) {
|
||||
orgID := c.Param("id")
|
||||
|
||||
u := user.CurrentUser(c)
|
||||
users, err := org.GetMembers(u, orgID)
|
||||
if err != nil {
|
||||
c.JSON(err.Status, common.RespError(err.Message))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, common.RespSuccess(users))
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/imdotdev/im.dev/server/internal/user"
|
||||
"github.com/imdotdev/im.dev/server/pkg/common"
|
||||
"github.com/imdotdev/im.dev/server/pkg/db"
|
||||
"github.com/imdotdev/im.dev/server/pkg/e"
|
||||
"github.com/imdotdev/im.dev/server/pkg/models"
|
||||
)
|
||||
|
||||
func GetNavbars(c *gin.Context) {
|
||||
navbars := make(models.Navbars, 0)
|
||||
|
||||
rows, err := db.Conn.Query("SELECT id,label,value,weight FROM navbar")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, common.RespError(e.Internal))
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
nv := &models.Navbar{}
|
||||
rows.Scan(&nv.ID, &nv.Label, &nv.Value, &nv.Weight)
|
||||
navbars = append(navbars, nv)
|
||||
}
|
||||
|
||||
sort.Sort(navbars)
|
||||
|
||||
c.JSON(http.StatusOK, common.RespSuccess(navbars))
|
||||
}
|
||||
|
||||
func SubmitNavbar(c *gin.Context) {
|
||||
nav := &models.Navbar{}
|
||||
c.Bind(&nav)
|
||||
|
||||
u := user.CurrentUser(c)
|
||||
if !u.Role.IsAdmin() {
|
||||
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if nav.ID == 0 {
|
||||
_, err = db.Conn.Exec("INSERT INTO navbar (label,value,weight) VALUES (?,?,?)",
|
||||
nav.Label, nav.Value, nav.Weight)
|
||||
} else {
|
||||
_, err = db.Conn.Exec("UPDATE navbar SET label=?,value=?,weight=? WHERE id=?", nav.Label, nav.Value, nav.Weight, nav.ID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Warn("submit navbar error", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, common.RespError(e.Internal))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, common.RespSuccess(nil))
|
||||
}
|
||||
|
||||
func DeleteNavbar(c *gin.Context) {
|
||||
u := user.CurrentUser(c)
|
||||
if !u.Role.IsAdmin() {
|
||||
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
_, err := db.Conn.Exec("DELETE FROM navbar WHERE id=?", id)
|
||||
if err != nil {
|
||||
logger.Warn("delete navbar error", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, common.RespError(e.Internal))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, common.RespSuccess(nil))
|
||||
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package org
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/imdotdev/im.dev/server/internal/interaction"
|
||||
"github.com/imdotdev/im.dev/server/pkg/db"
|
||||
"github.com/imdotdev/im.dev/server/pkg/e"
|
||||
"github.com/imdotdev/im.dev/server/pkg/log"
|
||||
"github.com/imdotdev/im.dev/server/pkg/models"
|
||||
"github.com/imdotdev/im.dev/server/pkg/utils"
|
||||
)
|
||||
|
||||
var logger = log.RootLogger.New("logger", "org")
|
||||
|
||||
func GetOrgByUserID(userID string) ([]*models.User, *e.Error) {
|
||||
orgs := make([]*models.User, 0)
|
||||
|
||||
rows, err := db.Conn.Query("SELECT org_id,role FROM org_member WHERE user_id=?", userID)
|
||||
if err != nil {
|
||||
logger.Warn("get user orgs error", "error", err)
|
||||
return nil, e.New(http.StatusInternalServerError, e.Internal)
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var oid, role string
|
||||
rows.Scan(&oid, &role)
|
||||
|
||||
org, ok := models.UsersMapCache[oid]
|
||||
if ok {
|
||||
org.Role = models.RoleType(role)
|
||||
orgs = append(orgs, org)
|
||||
}
|
||||
}
|
||||
|
||||
return orgs, nil
|
||||
}
|
||||
|
||||
func Create(o *models.User, userID string) *e.Error {
|
||||
o.ID = utils.GenID(models.IDTypeOrg)
|
||||
|
||||
tx, err := db.Conn.Begin()
|
||||
if err != nil {
|
||||
logger.Warn("start sql transaction error", "error", err)
|
||||
return e.New(http.StatusInternalServerError, e.Internal)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
_, err = tx.Exec("INSERT INTO user (id,type,username,nickname,created,updated) VALUES (?,?,?,?,?,?)", o.ID, models.IDTypeOrg, o.Username, o.Nickname, now, now)
|
||||
if err != nil {
|
||||
logger.Warn("add org error", "error", err)
|
||||
return e.New(http.StatusInternalServerError, e.Internal)
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO org_member (org_id,user_id,role,created) VALUES (?,?,?,?)", o.ID, userID, models.ROLE_ADMIN, now)
|
||||
if err != nil {
|
||||
logger.Warn("add org member error", "error", err)
|
||||
tx.Rollback()
|
||||
return e.New(http.StatusInternalServerError, e.Internal)
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMembers(user *models.User, orgID string) ([]*models.User, *e.Error) {
|
||||
rows, err := db.Conn.Query("SELECT user_id from org_member where org_id=?", orgID)
|
||||
if err != nil {
|
||||
logger.Warn("get org members error", "error", err)
|
||||
return nil, e.New(http.StatusInternalServerError, e.Internal)
|
||||
}
|
||||
|
||||
users := make([]*models.User, 0)
|
||||
for rows.Next() {
|
||||
var id string
|
||||
rows.Scan(&id)
|
||||
|
||||
u, ok := models.UsersMapCache[id]
|
||||
if ok {
|
||||
users = append(users, u)
|
||||
if user != nil {
|
||||
u.Followed = interaction.GetFollowed(u.ID, user.ID)
|
||||
u.Follows = interaction.GetFollows(u.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetMemberCount(orgID string) int {
|
||||
var count int
|
||||
err := db.Conn.QueryRow("SELECT count(*) FROM org_member WHERE org_id=?", orgID).Scan(&count)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
logger.Warn("get org member count error", "error", err)
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
export interface Org {
|
||||
id: string
|
||||
username: string
|
||||
nickname: string
|
||||
|
||||
avatar?: string
|
||||
cover?: string
|
||||
|
||||
email?: string
|
||||
website?: string
|
||||
location?: string
|
||||
tagline?: string
|
||||
about?: string
|
||||
stack?: string
|
||||
|
||||
twitter?: string
|
||||
github?: string
|
||||
weibo?: string
|
||||
|
||||
created?: string
|
||||
role?: string
|
||||
}
|
Loading…
Reference in new issue