I might be able to help propose a better model/auth rule if you were to actually post your whole schema. There is a lot of undefined types here still.
And can you explain the purpose of needing school and categories in your rule? I can assume, but better would be if you explain it to me like I am 5 what you are trying to do here.
ok, Classes belong to Schools,and a classes have courses and sections, classes belong to a category emmm, every school have many classes, and auth rule have two method:
If the user have full controller of the school, it can do everything in this school, like, add a course, add a classes…
If the user don’t have full controller of the school, now i have to judge does he have access to operate the category of this school.
category likes Primary School, Middle School, University
classes likes class one, grade one. class two, grade one
courses like math, art, physical…
I tested that this shcema loads, but I have not loaded data into the graph to test if the rules work (they should)
interface Base {
############################################################
# granted_access is used for ACL traversing in @auth rules #
############################################################
granted_access: [AccessControl]
created_at: DateTime! @search(by: [day])
updated_at: DateTime @search(by: [day])
deleted_at: DateTime @search
created_by: Int @search
updated_by: Int @search
deleted_by: Int @search
}
type Area {
code: String! @id
name: String!
short_name: String!
city_code: String
zip_code:String
merger_name:String!
lng: String
lat: String
pinyin: String
parent: Area @hasInverse(field:"child")
child: [Area] @hasInverse(field:"parent")
schools:[School] @hasInverse(field:"area")
}
enum SchoolType {
province
city
area
}
enum SchoolYYMode {
"""第三种方式"""
third_party
"""加盟"""
league
"""总校直营"""
hq
"""外机构"""
outside
}
enum SchoolStatus {
"""对应1 正常"""
normal
"""对应2 删除"""
deleted
"""对应3 未完善"""
not_impl
}
type School implements Base{
id: ID!
mid: Int
"""学校名称"""
school_name: String! @search
"""学校地址"""
address: String!
"""联系电话"""
phone: String! @search
"""成立日期"""
date: DateTime
"""管理人/联系人"""
admin: String
"""学校类型"""
school_type: SchoolType @search
"""学校运营类型"""
yy_mode: SchoolYYMode @search
"""简介"""
brief: String
"""学校状态"""
status: SchoolStatus @search
"""版本号"""
version: Int
"""拥有班级"""
classes: [Classes]
"""所属地区"""
area:Area! @hasInverse(field:"schools")
###############################################################################
# Using the Base.granted_access edge instead of School.administrator for ACL. #
###############################################################################
#"""管理员"""
#administrator:[UserAuthSchoolClasses] @hasInverse(field:"school")
has_categories: [Category] @hasInverse(field: "in_school")
}
"""科目表:"""
type Subject implements Base {
id: ID!
mid: Int
subject_name: String!
color: String!
teachers: [Teacher]
# FIXME: This type is undefined
# user_sort:[SortSubjectUser] @hasInverse(field:"subject")
}
"""研究院表:"""
type Academy implements Base{
id: ID!
mid: Int
academy_name:String! @search
province:String!
manager:Int!
introduce:String
phone:String!
managers:String
child_academy: [Academy] @hasInverse(field: "parent_academy")
parent_academy: Academy @hasInverse(field: "child_academy")
teachers: [Teacher] @hasInverse(field: "academy")
}
"""招考类型表:事业单位,省考,选调生,银行招考......"""
type RecruitingType implements Base{
id: ID!
mid: Int
name: String! @search
color: String! @search
}
"""班型表:刷题班,基础班,基础刷题班,冲刺班......"""
type ClassType implements Base{
id: ID!
mid: Int
name: String! @search
short_name: String! @search
}
"""课程类型:直播课,地面课......"""
type CourseType implements Base{
id: ID!
mid: Int
name: String! @search
color: String! @search
}
"""考试类型:面试,笔试......"""
type ExamType implements Base{
id: ID!
mid: Int
name: String! @search
color: String! @search
}
type Category implements Base {
id: ID!
mid: Int
name: String! @search
color: String! @search
in_school: School
}
"""班级"""
type Classes implements Base @auth(
add: {
or: [
# grant access to add if the class is for a school which has granted access to the user that is an admin
{ rule: "query ($mid: String!) { queryClasses { school { granted_access { user(filter: { mid: { eq: $mid } role: { eq: admin } }) { id } } } } }" },
# grant access to add if the class is for a school which has granted full access to the user
{ rule: "query ($mid: String!) { queryClasses { school { granted_access(filter: { level: { eq: FULL_CONTROL } }) { user(filter: { mid: { eq: $mid } }) { id } } } } }" },
# grant access to add if the class is in an category that has granted full access to the user and the user is granted any kind of access to the linked school
{ rule: "query ($mid: String!) { queryClasses { category { granted_access(filter: { level: { eq: FULL_CONTROL } }) { user(filter: { mid: { eq: $mid } }) { id } } } school { granted_access { user(filter: { mid: { eq: $mid } }) { id } } } } }" },
# (optional) grant access to a system script to load data
{ rule: "{$USER: { eq: \"SYSTEM\" }}" }
]
}
) {
id: ID!
name: String! @search
year: Int! @search
class_type: ClassType!
school: School! @hasInverse(field: "classes")
recruiting_type: RecruitingType!
is_full_day: Boolean! @search
start_date: DateTime! @search
end_date: DateTime! @search
course_type: CourseType!
exam_type: ExamType!
category: Category!
lend_teacher: Boolean!
courses: [Course] @hasInverse(field: "classes")
course_arrange: String
}
"""课程"""
type Course implements Base {
id: ID!
name: String! @search
subject: Subject!
school: School!
classes: Classes! @hasInverse(field: "courses")
sections: [CourseSection] @hasInverse(field: "course")
remark: String
finish: Boolean! @search
start_date: DateTime! @search
end_date: DateTime! @search
introduce: String
virtual: Boolean!
lend_teacher: Boolean!
share: Boolean!
merge: Boolean!
people_num: Int
}
"""节"""
type CourseSection implements Base {
id: ID!
name: String! @search
course: Course! @hasInverse(field: "sections")
identity: String!
date: DateTime! @search
start_time: DateTime! @search
end_time: DateTime! @search
teacher: [TeacherCourseInfo]
time_duration: Float
is_full_day: Boolean!
}
type TeacherWorkingMechanism implements Base{
id: ID!
mid: Int
name: String! @search
parent: TeacherWorkingMechanism @hasInverse(field: "child")
child: [TeacherWorkingMechanism] @hasInverse(field: "parent")
teachers: [Teacher] @hasInverse(field: "working_mechanism")
}
type TeacherPosition implements Base{
id: ID!
mid: Int
name: String! @search
teacher:[Teacher] @hasInverse(field: "position")
}
type TeacherBankAccount implements Base{
id: ID!
mid: Int
bank_name: String! @search
account: String! @search
owner:[Teacher] @hasInverse(field: "bank_account")
}
type National {
id: ID!
name: String! @search
}
enum Sex {
Man
Women
}
"""教师"""
type Teacher implements Base{
id:ID!
"""教师姓名"""
teacher_name:String
"""年龄"""
age:Int
"""艺名"""
stage_name:String!
"""研究院"""
academy:Academy
"""是否本校教师"""
regular_employee: Boolean
"""是否试用期"""
probation: Boolean
# """教师分组"""
# teacher_group:TeacherGroup
"""是否班主任"""
head_teacher:Boolean
"""转正日期"""
becoming_regular_date:DateTime
"""头像"""
avatar:String
"""手机号"""
mobile:String
"""微信号"""
wechat:String
"""工作机制"""
working_mechanism: TeacherWorkingMechanism @hasInverse(field: "teachers")
"""教师职位"""
position: TeacherPosition @hasInverse(field: "teacher")
"""简介"""
biography: String
"""教师性别"""
sex: Sex
"""民族"""
national: National
"""在职状态"""
status: Boolean
"""身份证号"""
id_card: String @search
"""银行卡"""
bank_account: [TeacherBankAccount] @hasInverse(field: "owner")
"""上课时长"""
class_hours: Float
"""工资"""
wages: Float
"""离职日期"""
quit_at: DateTime
"""入职日期"""
entry_at: DateTime
"""是否实时"""
is_realtime:Boolean
"""退款状态"""
refund_status:Boolean
"""余额"""
balance:Float
}
enum TeacherIdentity {
"""主讲"""
speaker
"""助教"""
TA
"""搭档"""
partner
"""助教(学)"""
TAStu
}
type TeacherCourseInfo implements Base {
id:ID!
course_section: CourseSection
teacher: Teacher
identity: TeacherIdentity
"""true-课老师,false-节老师"""
is_course: Boolean
class_pay: Float
settlement_standard:String
}
enum UserAuthRole {
admin
user
finance_user
}
###########################################################################################
# I am assuming this would be the type I refer to as "User" unique to an individual logon #
###########################################################################################
type UserAuth {
############################################################
# NOTE: you can have both the @id and an ID field together #
############################################################
id: ID!
mid: String! @id
##################################################
# Don't need specific access to the schools here #
##################################################
#schools:[UserAuthSchoolClasses] @hasInverse(field:"user")
role: UserAuthRole! @search
##########################################################################
# Reverse traversing of which resources the user has been granted access #
##########################################################################
has_access: [AccessControl]
}
################################
# Don't even need this anymore #
################################
#type UserAuthSchoolClasses {
# id: ID!
# school:School! @hasInverse(field:"administrator")
# school_full_controller:Boolean! @search
# categories:[Category]
# user: UserAuth! @hasInverse(field:"schools")
#}
enum ControlLevel {
FULL_CONTROL
EDITOR
VIEWER
}
###################################################
# Add nodes here for new user/access combinations #
###################################################
type AccessControl @auth(
####################################################
# Make this inaccessible to query outside of rules #
# NOTE: If you want to test query, you will need #
# to comment this rule out temporarily #
####################################################
query: { rule: "{$DENIED: { eq: \"DENIED\" }}" }
# FIXME: write a "add" rule or else anyone can create their own AccessControl granting themselves or anyone else access
# FIXME: write a "delete" rule or else anyone can delete anyone else's access
# FIXME: write a "update" rules or else anyone can steal anyone else's access
) {
id: ID!
######################################################################################
# Link to the interface of the implementing node which the user is granted to access #
######################################################################################
controls: [Base] @hasInverse(field: "granted_access")
user: UserAuth! @hasInverse(field: "has_access")
##################################################################
# What type of control to grant, useful for different CRUD rules #
##################################################################
level: ControlLevel @search
}
# Dgraph.Authorization {"VerificationKey":"xtjpk","Header":"x-d-token","Namespace":"xtjpk","Algo":"HS256","Audience":["user"]}
Look at the new type AccessControl which is linked to through Base which is implemented by Classes (should be Class probably) so the rule can then use those nodes for traveral.
Thank u.
I’ll fix my schema today, and try to understand the flow. By the way, I cloned dgraph in github and try to find auth module, but I think I can read this when I understand graphql fully.