Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
PyFund
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
陈涛
PyFund
Commits
0d718040
Commit
0d718040
authored
May 19, 2023
by
陈涛
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/main'
# Conflicts: # api/fund.py
parents
08812a66
f70f87e8
Changes
7
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
475 additions
and
340 deletions
+475
-340
fund.py
api/fund.py
+37
-7
permission.py
api/permission.py
+40
-35
dependencies.py
dependencies.py
+8
-2
token.py
exception/token.py
+5
-0
permission.py
model/permission.py
+4
-4
permission.py
schema/permission.py
+18
-292
permission.py
service/permission.py
+363
-0
No files found.
api/fund.py
View file @
0d718040
import
asyncio
import
datetime
import
datetime
from
typing
import
Union
,
Optional
from
typing
import
Union
,
Optional
...
@@ -6,12 +7,16 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
...
@@ -6,12 +7,16 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
from
fastapi
import
APIRouter
,
Depends
,
Query
,
Request
from
fastapi
import
APIRouter
,
Depends
,
Query
,
Request
from
motor.core
import
AgnosticCollection
from
motor.core
import
AgnosticCollection
from
pymongo
import
ReturnDocument
from
pymongo
import
ReturnDocument
from
exception.db
import
NotFundError
from
exception.db
import
NotFundError
from
exception.token
import
FundPermissionError
from
model
import
Response
,
PageResponse
,
Page
from
model
import
Response
,
PageResponse
,
Page
from
model.fund
import
FundType
,
StakingFund
,
NormalFund
,
FundStatus
from
model.fund
import
FundType
,
StakingFund
,
NormalFund
,
FundStatus
from
dependencies
import
get_current_user
,
get_fund_collect
,
get_scheduler
from
dependencies
import
get_current_user
,
get_fund_collect
,
get_scheduler
,
get_permission_user_collect
,
\
get_permission_role_collect
from
schema.fund
import
CreateFund
,
UpdateFund
from
schema.fund
import
CreateFund
,
UpdateFund
from
service.scheduler
import
delete_nav_task
,
calculate_nav_task
,
get_next_execute_time
from
service.permission
import
create_default_role_and_user
,
check_permission
from
service.scheduler
import
delete_nav_task
,
calculate_nav_task
from
tools.jwt_tools
import
User
from
tools.jwt_tools
import
User
router
=
APIRouter
()
router
=
APIRouter
()
...
@@ -28,8 +33,10 @@ fund_type_map = {
...
@@ -28,8 +33,10 @@ fund_type_map = {
async
def
create
(
async
def
create
(
create_fund
:
CreateFund
,
create_fund
:
CreateFund
,
user
:
User
=
Depends
(
get_current_user
),
user
:
User
=
Depends
(
get_current_user
),
scheduler
:
AsyncIOScheduler
=
Depends
(
get_scheduler
),
fund_collect
:
AgnosticCollection
=
Depends
(
get_fund_collect
),
fund_collect
:
AgnosticCollection
=
Depends
(
get_fund_collect
),
scheduler
:
AsyncIOScheduler
=
Depends
(
get_scheduler
)
permission_user_collect
:
AgnosticCollection
=
Depends
(
get_permission_user_collect
),
permission_role_collect
:
AgnosticCollection
=
Depends
(
get_permission_role_collect
)
):
):
create_model
=
fund_type_map
[
create_fund
.
fund_type
](
**
create_fund
.
dict
(),
nodes
=
[],
**
user
.
db_save
())
create_model
=
fund_type_map
[
create_fund
.
fund_type
](
**
create_fund
.
dict
(),
nodes
=
[],
**
user
.
db_save
())
create_model
.
nav
=
create_model
.
base_nav
create_model
.
nav
=
create_model
.
base_nav
...
@@ -37,6 +44,8 @@ async def create(
...
@@ -37,6 +44,8 @@ async def create(
data
=
create_model
.
dict
()
data
=
create_model
.
dict
()
response_model
=
fund_type_map
[
data
[
'fund_type'
]]
response_model
=
fund_type_map
[
data
[
'fund_type'
]]
await
fund_collect
.
insert_one
(
data
)
await
fund_collect
.
insert_one
(
data
)
# await calculate_nav_task(data['id'], scheduler, fund_collect, user.id)
await
create_default_role_and_user
(
data
[
'id'
],
user
.
email
,
permission_user_collect
,
permission_role_collect
)
job_id
=
f
"calculate_nav_{data['id']}"
job_id
=
f
"calculate_nav_{data['id']}"
time_obj
=
datetime
.
datetime
.
strptime
(
data
[
"settlement_time"
],
"
%
H:
%
M"
)
time_obj
=
datetime
.
datetime
.
strptime
(
data
[
"settlement_time"
],
"
%
H:
%
M"
)
scheduler
.
add_job
(
scheduler
.
add_job
(
...
@@ -47,7 +56,7 @@ async def create(
...
@@ -47,7 +56,7 @@ async def create(
minute
=
time_obj
.
minute
,
minute
=
time_obj
.
minute
,
args
=
[
data
[
"id"
]],
args
=
[
data
[
"id"
]],
id
=
job_id
,
id
=
job_id
,
misfire_grace_time
=
60
*
60
misfire_grace_time
=
60
*
60
)
)
return
Response
[
response_model
](
data
=
response_model
(
**
data
))
return
Response
[
response_model
](
data
=
response_model
(
**
data
))
...
@@ -91,8 +100,15 @@ async def update(
...
@@ -91,8 +100,15 @@ async def update(
async
def
get
(
async
def
get
(
fund_id
:
str
,
fund_id
:
str
,
user
:
User
=
Depends
(
get_current_user
),
user
:
User
=
Depends
(
get_current_user
),
fund_collect
:
AgnosticCollection
=
Depends
(
get_fund_collect
)
fund_collect
:
AgnosticCollection
=
Depends
(
get_fund_collect
),
permission_user_collect
:
AgnosticCollection
=
Depends
(
get_permission_user_collect
),
permission_role_collect
:
AgnosticCollection
=
Depends
(
get_permission_role_collect
)
):
):
# 验证权限
if
not
await
check_permission
([
'data_permission.fund.query_info'
],
fund_id
,
user
.
email
,
permission_user_collect
,
permission_role_collect
):
raise
FundPermissionError
()
data
=
await
fund_collect
.
find_one
({
'id'
:
fund_id
,
'user_id'
:
user
.
id
})
data
=
await
fund_collect
.
find_one
({
'id'
:
fund_id
,
'user_id'
:
user
.
id
})
assert
data
,
NotFundError
()
assert
data
,
NotFundError
()
...
@@ -110,9 +126,23 @@ async def get(
...
@@ -110,9 +126,23 @@ async def get(
fund_type
:
Optional
[
FundType
]
=
Query
(
default
=
None
,
description
=
'基金类型'
),
fund_type
:
Optional
[
FundType
]
=
Query
(
default
=
None
,
description
=
'基金类型'
),
fund_status
:
FundStatus
=
None
,
fund_status
:
FundStatus
=
None
,
user
:
User
=
Depends
(
get_current_user
),
user
:
User
=
Depends
(
get_current_user
),
fund_collect
:
AgnosticCollection
=
Depends
(
get_fund_collect
)
fund_collect
:
AgnosticCollection
=
Depends
(
get_fund_collect
),
permission_user_collect
:
AgnosticCollection
=
Depends
(
get_permission_user_collect
),
permission_role_collect
:
AgnosticCollection
=
Depends
(
get_permission_role_collect
)
):
):
query
=
{
"user_id"
:
user
.
id
}
relation_funds
=
await
permission_user_collect
.
find
({
"email"
:
user
.
email
})
.
to_list
(
length
=
None
)
tasks
=
{}
async
with
asyncio
.
TaskGroup
()
as
g
:
for
item
in
relation_funds
:
fund_id
=
item
[
'fund_id'
]
task
=
g
.
create_task
(
check_permission
([
'data_permission.fund.query_info'
],
fund_id
,
user
.
email
,
permission_user_collect
,
permission_role_collect
))
tasks
[
fund_id
]
=
task
auth_fund_list
=
[
k
for
k
,
v
in
tasks
.
items
()
if
v
.
result
()]
query
=
{
"id"
:
{
'$in'
:
auth_fund_list
}}
if
fund_type
:
if
fund_type
:
query
.
update
({
"fund_type"
:
fund_type
})
query
.
update
({
"fund_type"
:
fund_type
})
if
fund_status
:
if
fund_status
:
...
...
api/permission.py
View file @
0d718040
from
motor.core
import
AgnosticCollection
from
motor.core
import
AgnosticCollection
import
dependencies
from
exception.db
import
ExistDataError
,
NotFundError
from
model
import
BaseResponse
,
Response
,
PageResponse
,
Page
from
fastapi
import
APIRouter
,
Depends
,
Query
from
model.permission
import
Role
,
UserInfo
from
dependencies
import
get_current_user
,
get_permission_user_collect
,
get_permission_role_collect
from
schema.permission
import
CreateRole
,
CreateUserInfo
from
exception.token
import
FundPermissionError
from
schema.beacon
import
Validator
,
ValidatorDeposit
,
ValidatorBlock
,
ValidatorIncome
,
Epoch
from
model
import
BaseResponse
,
Response
from
service.beacon
import
BeaconChaService
from
fastapi
import
APIRouter
,
Depends
from
schema.permission
import
CreateUserInfo
from
service.permission
import
check_permission
from
tools.jwt_tools
import
User
from
tools.jwt_tools
import
User
router
=
APIRouter
()
router
=
APIRouter
()
# @router.post('/',
@
router
.
post
(
'/user/'
,
response_model
=
BaseResponse
,
summary
=
'添加账号权限'
,
description
=
'添加账号权限'
)
async
def
get_permission
(
create_user_info
:
CreateUserInfo
,
user
:
User
=
Depends
(
get_current_user
),
permission_user_collect
:
AgnosticCollection
=
Depends
(
get_permission_user_collect
),
permission_role_collect
:
AgnosticCollection
=
Depends
(
get_permission_role_collect
)
):
# 检查是否有权限添加用户
assert
'admin'
not
in
create_user_info
.
roles
,
FundPermissionError
()
is_auth
=
await
check_permission
([
f
'member_permission.{role}.add'
for
role
in
create_user_info
.
roles
],
create_user_info
.
fund_id
,
user
.
email
,
permission_user_collect
,
permission_role_collect
)
if
is_auth
:
query
=
{
"fund_id"
:
create_user_info
.
fund_id
,
"email"
:
create_user_info
.
email
}
update
=
{
"$addToSet"
:
{
"role"
:
{
"$each"
:
create_user_info
.
roles
}}}
await
permission_user_collect
.
update_one
(
query
,
update
,
upsert
=
True
)
return
Response
(
data
=
''
)
else
:
raise
FundPermissionError
()
# @router.post('/role',
# response_model=BaseResponse,
# response_model=BaseResponse,
# summary='添加
权限
',
# summary='添加
角色
',
# description='添加
权限
')
# description='添加
角色
')
# async def add_
permission
(
# async def add_
role
(
#
permission: CreatePermission
,
#
role: CreateRole
,
# # user: User = Depends(dependencies.get_current_user),
# # user: User = Depends(dependencies.get_current_user),
# permission_collect: AgnosticCollection = Depends(dependencies.get_permission_collect)
# permission_collect: AgnosticCollection = Depends(dependencies.get_permission_collect)
# ):
# ):
# data = await permission_collect.find_one({'name':
permission.name, 'group': permission.group
})
# data = await permission_collect.find_one({'name':
role.name, 'fund_id': role.fund_id
})
# if not data:
# if not data:
# db_data =
Permission(**permission
.dict())
# db_data =
Role(**role
.dict())
# await permission_collect.insert_one(db_data.dict())
# await permission_collect.insert_one(db_data.dict())
# return Response[
Permission
](data=db_data)
# return Response[
Role
](data=db_data)
# else:
# else:
# raise ExistDataError(message='该分组下已存在此权限')
# raise ExistDataError(message='该机构下已存在此角色')
@
router
.
post
(
'/role'
,
response_model
=
BaseResponse
,
summary
=
'添加角色'
,
description
=
'添加角色'
)
async
def
add_role
(
role
:
CreateRole
,
# user: User = Depends(dependencies.get_current_user),
permission_collect
:
AgnosticCollection
=
Depends
(
dependencies
.
get_permission_collect
)
):
data
=
await
permission_collect
.
find_one
({
'name'
:
role
.
name
,
'org_id'
:
role
.
org_id
})
if
not
data
:
db_data
=
Role
(
**
role
.
dict
())
await
permission_collect
.
insert_one
(
db_data
.
dict
())
return
Response
[
Role
](
data
=
db_data
)
else
:
raise
ExistDataError
(
message
=
'该机构下已存在此角色'
)
dependencies.py
View file @
0d718040
...
@@ -55,8 +55,14 @@ def get_nav_collect(mongodb_manager: AioMongodbManager = Depends(get_mongodb_man
...
@@ -55,8 +55,14 @@ def get_nav_collect(mongodb_manager: AioMongodbManager = Depends(get_mongodb_man
return
mongodb_manager
.
get_client
(
name
=
'pyfund'
,
db
=
'pyfund'
,
collect
=
'nav'
)
return
mongodb_manager
.
get_client
(
name
=
'pyfund'
,
db
=
'pyfund'
,
collect
=
'nav'
)
def
get_permission_collect
(
mongodb_manager
:
AioMongodbManager
=
Depends
(
get_mongodb_manager
))
->
AgnosticCollection
:
def
get_permission_user_collect
(
return
mongodb_manager
.
get_client
(
name
=
'pyfund'
,
db
=
'pyfund'
,
collect
=
'permission'
)
mongodb_manager
:
AioMongodbManager
=
Depends
(
get_mongodb_manager
))
->
AgnosticCollection
:
return
mongodb_manager
.
get_client
(
name
=
'pyfund'
,
db
=
'pyfund'
,
collect
=
'permission_user'
)
def
get_permission_role_collect
(
mongodb_manager
:
AioMongodbManager
=
Depends
(
get_mongodb_manager
))
->
AgnosticCollection
:
return
mongodb_manager
.
get_client
(
name
=
'pyfund'
,
db
=
'pyfund'
,
collect
=
'permission_role'
)
# 获取redis Client
# 获取redis Client
...
...
exception/token.py
View file @
0d718040
...
@@ -3,3 +3,8 @@ from exception import MyException
...
@@ -3,3 +3,8 @@ from exception import MyException
class
TokenError
(
MyException
):
class
TokenError
(
MyException
):
pass
pass
class
FundPermissionError
(
MyException
):
message
=
'权限错误'
status
=
400
model/permission.py
View file @
0d718040
...
@@ -3,16 +3,16 @@ from pydantic import Field
...
@@ -3,16 +3,16 @@ from pydantic import Field
from
model
import
MyBaseModel
from
model
import
MyBaseModel
class
PermissionTable
(
MyBaseModel
):
#
class PermissionTable(MyBaseModel):
fund_id
:
str
=
Field
(
...
,
description
=
'基金id'
)
#
fund_id: str = Field(..., description='基金id')
data
:
Dict
[
str
,
List
[
str
]]
=
Field
({},
description
=
'权限表'
)
#
data: Dict[str, List[str]] = Field({}, description='权限表')
class
Role
(
MyBaseModel
):
class
Role
(
MyBaseModel
):
name
:
str
=
Field
(
...
,
description
=
'角色名'
)
name
:
str
=
Field
(
...
,
description
=
'角色名'
)
fund_id
:
str
=
Field
(
...
,
description
=
'基金id'
)
fund_id
:
str
=
Field
(
...
,
description
=
'基金id'
)
system
:
bool
=
Field
(
False
,
description
=
'系统创建'
)
system
:
bool
=
Field
(
False
,
description
=
'系统创建'
)
permissions
:
Dict
[
str
,
List
[
str
]]
=
Field
({},
description
=
'拥有
权限'
)
permissions
:
List
[
str
]
=
Field
([],
description
=
'拥有的
权限'
)
remark
:
str
=
Field
(
None
,
description
=
'备注'
)
remark
:
str
=
Field
(
None
,
description
=
'备注'
)
...
...
schema/permission.py
View file @
0d718040
...
@@ -3,309 +3,35 @@ from typing import List, Dict
...
@@ -3,309 +3,35 @@ from typing import List, Dict
from
pydantic
import
Field
,
BaseModel
from
pydantic
import
Field
,
BaseModel
class
PermissionItem
:
# class PermissionItem:
def
__init__
(
self
,
code
,
label
,
children
):
# def __init__(self, code, label, children):
self
.
code
=
code
# self.code = code
self
.
label
=
label
# self.label = label
self
.
children
=
children
# self.children = children
#
# def dict(self):
# return {
# "code": self.code,
# "label": self.label,
# "children": self.children
# }
def
dict
(
self
):
return
{
"code"
:
self
.
code
,
"label"
:
self
.
label
,
"children"
:
self
.
children
}
default_permission_table
=
[
{
"code"
:
'data_permission'
,
"label"
:
"数据管理权限"
,
"children"
:
[
# 基金管理
{
"code"
:
"fund"
,
"label"
:
'基金管理'
,
"children"
:
[
{
"code"
:
"query_info"
,
"label"
:
"查询基金基础信息"
},
{
"code"
:
"update_info"
,
"label"
:
"更新基金基础信息"
},
{
"code"
:
"query_list"
,
"label"
:
"查询基金列表"
},
{
"code"
:
"bill_page"
,
"label"
:
"访问账目页面"
},
]
},
# 质押节点
{
"code"
:
"node"
,
"label"
:
'质押节点'
,
"children"
:
[
{
"code"
:
"bind_node"
,
"label"
:
"绑定节点"
},
{
"code"
:
"untie_node"
,
"label"
:
"解绑节点"
},
{
"code"
:
"query_node"
,
"label"
:
"查询节点"
}
]
},
# 账目-申购/赎回
{
"code"
:
"sub_redeem_bill"
,
"label"
:
'账目-申购/赎回'
,
"children"
:
[
{
"code"
:
"add"
,
"label"
:
"添加"
},
{
"code"
:
"delete"
,
"label"
:
"删除"
},
{
"code"
:
"edit"
,
"label"
:
"修改"
},
{
"code"
:
"query"
,
"label"
:
"查询"
},
]
},
# 账目-换币
{
"code"
:
"swap_bill"
,
"label"
:
'账目-换币'
,
"children"
:
[
{
"code"
:
"add"
,
"label"
:
"添加"
},
{
"code"
:
"delete"
,
"label"
:
"删除"
},
{
"code"
:
"edit"
,
"label"
:
"修改"
},
{
"code"
:
"query"
,
"label"
:
"查询"
},
]
},
# 账目-质押
{
"code"
:
"staking_bill"
,
"label"
:
'账目-质押'
,
"children"
:
[
{
"code"
:
"add"
,
"label"
:
"添加"
},
{
"code"
:
"delete"
,
"label"
:
"删除"
},
{
"code"
:
"edit"
,
"label"
:
"修改"
},
{
"code"
:
"query"
,
"label"
:
"查询"
},
]
},
# 账目-调整账户
{
"code"
:
"adjust_bill"
,
"label"
:
'账目-调整账户'
,
"children"
:
[
{
"code"
:
"add"
,
"label"
:
"添加"
},
{
"code"
:
"delete"
,
"label"
:
"删除"
},
{
"code"
:
"edit"
,
"label"
:
"修改"
},
{
"code"
:
"query"
,
"label"
:
"查询"
},
]
},
# 净值管理
{
"code"
:
"nav"
,
"label"
:
'净值管理'
,
"children"
:
[
{
"code"
:
"recalculate"
,
"label"
:
"基金重新计算净值"
},
{
"code"
:
"add"
,
"label"
:
"新增"
},
{
"code"
:
"delete"
,
"label"
:
"删除"
},
{
"code"
:
"edit"
,
"label"
:
"修改"
},
{
"code"
:
"query"
,
"label"
:
"查询"
},
]
},
]
},
{
"code"
:
'role_permission'
,
"label"
:
"角色管理权限"
,
"children"
:
[
# 角色的管理
{
"code"
:
"role"
,
"label"
:
'角色管理'
,
"children"
:
[
{
"code"
:
"add"
,
"label"
:
"添加"
},
{
"code"
:
"delete"
,
"label"
:
"删除"
},
{
"code"
:
"edit"
,
"label"
:
"修改"
},
{
"code"
:
"query"
,
"label"
:
"查询"
},
]
},
]
},
{
"code"
:
'member_permission'
,
"label"
:
"人员管理权限"
,
"children"
:
[
# 基金经理人员的管理
{
"code"
:
"fund_manager"
,
"label"
:
'基金经理'
,
"children"
:
[
{
"code"
:
"add"
,
"label"
:
"添加"
},
{
"code"
:
"delete"
,
"label"
:
"删除"
},
{
"code"
:
"edit"
,
"label"
:
"修改"
},
{
"code"
:
"query"
,
"label"
:
"查询"
},
]
},
# 基金经理助理
{
"code"
:
"fund_manager_assistant"
,
"label"
:
'基金经理助理'
,
"children"
:
[
{
"code"
:
"add"
,
"label"
:
"添加"
},
{
"code"
:
"delete"
,
"label"
:
"删除"
},
{
"code"
:
"edit"
,
"label"
:
"修改"
},
{
"code"
:
"query"
,
"label"
:
"查询"
},
]
},
]
}
]
default_role_table
=
[
{
"name"
:
'admin'
,
"label"
:
"基金创建人"
,
"role"
:
[
'all'
]
},
{
"name"
:
'fund_manager'
,
"label"
:
"基金经理"
,
"role"
:
[
'all'
]
},
{
"name"
:
'fund_manager_assistant'
,
"label"
:
"基金经理助理"
,
"role"
:
[]
}
]
data
=
[]
for
item
in
default_permission_table
:
for
i
in
item
[
'children'
]:
base_code
=
i
[
'code'
]
for
x
in
i
[
'children'
]:
data
.
append
(
f
'{base_code}.{x["code"]}'
)
print
(
data
)
# 接口请求模型 创建
# 接口请求模型 创建
class
CreatePermissionTable
(
BaseModel
):
#
class CreatePermissionTable(BaseModel):
fund_id
:
str
=
Field
(
...
,
description
=
'基金id'
)
#
fund_id: str = Field(..., description='基金id')
data
:
Dict
[
str
,
List
[
str
]]
=
Field
(
default_permission_table
,
description
=
'权限表'
)
# data: Dict[str, List[str]] = Field(...
, description='权限表')
class
CreateRole
(
BaseModel
):
class
CreateRole
(
BaseModel
):
name
:
str
=
Field
(
...
,
description
=
'角色名'
)
name
:
str
=
Field
(
...
,
description
=
'角色名'
)
label
:
str
=
Field
(
...
,
description
=
'展示名'
)
fund_id
:
str
=
Field
(
...
,
description
=
'基金id'
)
fund_id
:
str
=
Field
(
...
,
description
=
'基金id'
)
permissions
:
Dict
[
str
,
List
[
str
]]
=
Field
({},
description
=
'拥有
权限'
)
permissions
:
List
[
str
]
=
Field
(
...
,
description
=
'拥有的
权限'
)
remark
:
str
=
Field
(
None
,
description
=
'备注'
)
remark
:
str
=
Field
(
None
,
description
=
'备注'
)
class
CreateUserInfo
(
BaseModel
):
class
CreateUserInfo
(
BaseModel
):
fund_id
:
str
=
Field
(
...
,
description
=
'基金id'
)
fund_id
:
str
=
Field
(
...
,
description
=
'基金id'
)
email
:
str
=
Field
(
...
,
description
=
'用户中心email'
)
email
:
str
=
Field
(
...
,
description
=
'用户中心email'
)
role
:
List
[
str
]
=
Field
([]
,
description
=
'角色'
)
role
s
:
List
[
str
]
=
Field
(
...
,
description
=
'角色'
)
service/permission.py
0 → 100644
View file @
0d718040
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment