@drax/identity-back 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/factory/TenantServiceFactory.js +24 -0
- package/dist/factory/UserServiceFactory.js +1 -1
- package/dist/graphql/resolvers/role.resolvers.js +19 -2
- package/dist/graphql/resolvers/tenant.resolvers.js +121 -0
- package/dist/graphql/resolvers/user.resolvers.js +14 -3
- package/dist/graphql/types/tenant.graphql +28 -0
- package/dist/graphql/types/user.graphql +3 -0
- package/dist/index.js +6 -3
- package/dist/interfaces/ITenant.js +1 -0
- package/dist/interfaces/ITenantRepository.js +1 -0
- package/dist/middleware/rbacMiddleware.js +2 -5
- package/dist/models/TenantModel.js +18 -0
- package/dist/models/UserModel.js +5 -0
- package/dist/permissions/IdentityPermissions.js +5 -0
- package/dist/rbac/Rbac.js +6 -0
- package/dist/repository/mongo/RoleMongoRepository.js +7 -6
- package/dist/repository/mongo/TenantMongoRepository.js +45 -0
- package/dist/repository/mongo/UserMongoRepository.js +19 -6
- package/dist/repository/sqlite/RoleSqliteRepository.js +24 -5
- package/dist/repository/sqlite/TenantSqliteRepository.js +106 -0
- package/dist/repository/sqlite/UserSqliteRepository.js +43 -37
- package/dist/routes/RoleRoutes.js +17 -2
- package/dist/routes/TenantRoutes.js +183 -0
- package/dist/routes/UserRoutes.js +14 -2
- package/dist/services/RoleService.js +0 -5
- package/dist/services/TenantService.js +59 -0
- package/dist/services/UserService.js +1 -1
- package/dist/utils/AuthUtils.js +4 -3
- package/dist/zod/TenantZod.js +8 -0
- package/package.json +2 -2
- package/src/factory/TenantServiceFactory.ts +33 -0
- package/src/factory/UserServiceFactory.ts +1 -1
- package/src/graphql/resolvers/role.resolvers.ts +21 -3
- package/src/graphql/resolvers/tenant.resolvers.ts +119 -0
- package/src/graphql/resolvers/user.resolvers.ts +14 -6
- package/src/graphql/types/tenant.graphql +28 -0
- package/src/graphql/types/user.graphql +3 -0
- package/src/index.ts +6 -0
- package/src/interfaces/ITenant.ts +9 -0
- package/src/interfaces/ITenantRepository.ts +15 -0
- package/src/interfaces/IUser.ts +4 -1
- package/src/middleware/rbacMiddleware.ts +6 -8
- package/src/models/TenantModel.ts +32 -0
- package/src/models/UserModel.ts +5 -0
- package/src/permissions/IdentityPermissions.ts +7 -0
- package/src/rbac/Rbac.ts +11 -3
- package/src/repository/mongo/RoleMongoRepository.ts +7 -6
- package/src/repository/mongo/TenantMongoRepository.ts +62 -0
- package/src/repository/mongo/UserMongoRepository.ts +20 -6
- package/src/repository/sqlite/RoleSqliteRepository.ts +27 -6
- package/src/repository/sqlite/TenantSqliteRepository.ts +139 -0
- package/src/repository/sqlite/UserSqliteRepository.ts +52 -40
- package/src/routes/RoleRoutes.ts +17 -3
- package/src/routes/TenantRoutes.ts +178 -0
- package/src/routes/UserRoutes.ts +14 -4
- package/src/services/RoleService.ts +1 -4
- package/src/services/TenantService.ts +74 -0
- package/src/services/UserService.ts +1 -1
- package/src/utils/AuthUtils.ts +4 -3
- package/src/zod/TenantZod.ts +14 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/types/factory/TenantServiceFactory.d.ts +4 -0
- package/types/factory/TenantServiceFactory.d.ts.map +1 -0
- package/types/graphql/resolvers/role.resolvers.d.ts.map +1 -1
- package/types/graphql/resolvers/tenant.resolvers.d.ts +44 -0
- package/types/graphql/resolvers/tenant.resolvers.d.ts.map +1 -0
- package/types/graphql/resolvers/user.resolvers.d.ts +2 -1
- package/types/graphql/resolvers/user.resolvers.d.ts.map +1 -1
- package/types/index.d.ts +4 -1
- package/types/index.d.ts.map +1 -1
- package/types/interfaces/ITenant.d.ts +7 -0
- package/types/interfaces/ITenant.d.ts.map +1 -0
- package/types/interfaces/ITenantRepository.d.ts +15 -0
- package/types/interfaces/ITenantRepository.d.ts.map +1 -0
- package/types/interfaces/IUser.d.ts +4 -0
- package/types/interfaces/IUser.d.ts.map +1 -1
- package/types/middleware/rbacMiddleware.d.ts.map +1 -1
- package/types/models/TenantModel.d.ts +16 -0
- package/types/models/TenantModel.d.ts.map +1 -0
- package/types/models/UserModel.d.ts.map +1 -1
- package/types/permissions/IdentityPermissions.d.ts +6 -1
- package/types/permissions/IdentityPermissions.d.ts.map +1 -1
- package/types/rbac/Rbac.d.ts +4 -2
- package/types/rbac/Rbac.d.ts.map +1 -1
- package/types/repository/mongo/RoleMongoRepository.d.ts.map +1 -1
- package/types/repository/mongo/TenantMongoRepository.d.ts +15 -0
- package/types/repository/mongo/TenantMongoRepository.d.ts.map +1 -0
- package/types/repository/mongo/UserMongoRepository.d.ts +2 -2
- package/types/repository/mongo/UserMongoRepository.d.ts.map +1 -1
- package/types/repository/sqlite/RoleSqliteRepository.d.ts +2 -0
- package/types/repository/sqlite/RoleSqliteRepository.d.ts.map +1 -1
- package/types/repository/sqlite/TenantSqliteRepository.d.ts +19 -0
- package/types/repository/sqlite/TenantSqliteRepository.d.ts.map +1 -0
- package/types/repository/sqlite/UserSqliteRepository.d.ts +4 -2
- package/types/repository/sqlite/UserSqliteRepository.d.ts.map +1 -1
- package/types/routes/RoleRoutes.d.ts.map +1 -1
- package/types/routes/TenantRoutes.d.ts +4 -0
- package/types/routes/TenantRoutes.d.ts.map +1 -0
- package/types/routes/UserRoutes.d.ts.map +1 -1
- package/types/services/RoleService.d.ts.map +1 -1
- package/types/services/TenantService.d.ts +16 -0
- package/types/services/TenantService.d.ts.map +1 -0
- package/types/utils/AuthUtils.d.ts +3 -2
- package/types/utils/AuthUtils.d.ts.map +1 -1
- package/types/zod/TenantZod.d.ts +10 -0
- package/types/zod/TenantZod.d.ts.map +1 -0
|
@@ -14,6 +14,13 @@ enum IdentityPermissions {
|
|
|
14
14
|
ManageRole = "role:manage",
|
|
15
15
|
PermissionsRole = "role:permissions",
|
|
16
16
|
|
|
17
|
+
|
|
18
|
+
CreateTenant = "tenant:create",
|
|
19
|
+
UpdateTenant = "tenant:update",
|
|
20
|
+
DeleteTenant = "tenant:delete",
|
|
21
|
+
ViewTenant = "tenant:view",
|
|
22
|
+
ManageTenant = "tenant:manage",
|
|
23
|
+
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
export default IdentityPermissions;
|
package/src/rbac/Rbac.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {IRole} from "../interfaces/IRole";
|
|
2
2
|
import {IJwtUser} from "../interfaces/IJwtUser";
|
|
3
3
|
import UnauthorizedError from "../errors/UnauthorizedError.js";
|
|
4
4
|
|
|
5
5
|
class Rbac{
|
|
6
|
-
private role:
|
|
6
|
+
private role: IRole;
|
|
7
7
|
private authUser: IJwtUser;
|
|
8
8
|
|
|
9
|
-
constructor(authUser: IJwtUser, role:
|
|
9
|
+
constructor(authUser: IJwtUser, role: IRole) {
|
|
10
10
|
this.authUser = authUser;
|
|
11
11
|
this.role = role;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
get getRole() {
|
|
15
|
+
return this.role
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get getAuthUser() {
|
|
19
|
+
return this.authUser
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
hasPermission(requiredPermission: string): boolean {
|
|
15
23
|
if (!this.authUser || !this.role || !this.role.permissions || this.role.permissions.length === 0) {
|
|
16
24
|
return false;
|
|
@@ -10,31 +10,32 @@ class RoleMongoRepository implements IRoleRepository{
|
|
|
10
10
|
async create(roleData: IRole): Promise<IRole> {
|
|
11
11
|
const role : mongoose.HydratedDocument<IRole> = new RoleModel(roleData)
|
|
12
12
|
await role.save()
|
|
13
|
+
await role.populate('childRoles')
|
|
13
14
|
return role
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
async update(id: mongoose.Types.ObjectId | string, roleData: IRole): Promise<IRole> {
|
|
17
|
-
const role : mongoose.HydratedDocument<IRole> = await RoleModel.findOneAndUpdate({_id: id}, roleData, {new: true}).exec()
|
|
18
|
+
const role : mongoose.HydratedDocument<IRole> = await RoleModel.findOneAndUpdate({_id: id}, roleData, {new: true}).populate('childRoles').exec()
|
|
18
19
|
return role
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
async delete(id: mongoose.Types.ObjectId): Promise<boolean> {
|
|
22
|
-
const result : DeleteResult = await RoleModel.deleteOne(id).exec()
|
|
23
|
+
const result : DeleteResult = await RoleModel.deleteOne({_id: id}).exec()
|
|
23
24
|
return result.deletedCount == 1
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
async findById(id: mongoose.Types.ObjectId): Promise<IRole | null>{
|
|
27
|
-
const role: mongoose.HydratedDocument<IRole> | null = await RoleModel.findById(id).exec()
|
|
28
|
+
const role: mongoose.HydratedDocument<IRole> | null = await RoleModel.findById(id).populate('childRoles').exec()
|
|
28
29
|
return role
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
async findByName(name: string): Promise<IRole | null>{
|
|
32
|
-
const role: mongoose.HydratedDocument<IRole> | null = await RoleModel.findOne({name}).exec()
|
|
33
|
+
const role: mongoose.HydratedDocument<IRole> | null = await RoleModel.findOne({name}).populate('childRoles').exec()
|
|
33
34
|
return role
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
async fetchAll(): Promise<IRole[]>{
|
|
37
|
-
const roles: mongoose.HydratedDocument<IRole>[] = await RoleModel.find().exec()
|
|
38
|
+
const roles: mongoose.HydratedDocument<IRole>[] = await RoleModel.find().populate('childRoles').exec()
|
|
38
39
|
return roles
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -48,7 +49,7 @@ class RoleMongoRepository implements IRoleRepository{
|
|
|
48
49
|
]
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
const options = {page, limit} as PaginateOptions
|
|
52
|
+
const options = {populate: ['childRoles'], page, limit} as PaginateOptions
|
|
52
53
|
const roles: PaginateResult<IRole> = await RoleModel.paginate(query, options)
|
|
53
54
|
return {
|
|
54
55
|
page: page,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {TenantModel} from "../../models/TenantModel.js";
|
|
2
|
+
import {ITenant} from '../../interfaces/ITenant'
|
|
3
|
+
import {ITenantRepository} from '../../interfaces/ITenantRepository'
|
|
4
|
+
import {IPaginateFilter, IPaginateResult, mongoose} from "@drax/common-back";
|
|
5
|
+
import {FilterQuery, PaginateOptions, PaginateResult} from "mongoose";
|
|
6
|
+
import {DeleteResult} from "mongodb";
|
|
7
|
+
|
|
8
|
+
class TenantMongoRepository implements ITenantRepository{
|
|
9
|
+
|
|
10
|
+
async create(tenantData: ITenant): Promise<ITenant> {
|
|
11
|
+
const tenant : mongoose.HydratedDocument<ITenant> = new TenantModel(tenantData)
|
|
12
|
+
await tenant.save()
|
|
13
|
+
return tenant
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async update(id: mongoose.Types.ObjectId | string, tenantData: ITenant): Promise<ITenant> {
|
|
17
|
+
const tenant : mongoose.HydratedDocument<ITenant> = await TenantModel.findOneAndUpdate({_id: id}, tenantData, {new: true}).exec()
|
|
18
|
+
return tenant
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async delete(id: mongoose.Types.ObjectId): Promise<boolean> {
|
|
22
|
+
const result : DeleteResult = await TenantModel.deleteOne({_id:id}).exec()
|
|
23
|
+
return result.deletedCount == 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async findById(id: mongoose.Types.ObjectId): Promise<ITenant | null>{
|
|
27
|
+
const tenant: mongoose.HydratedDocument<ITenant> | null = await TenantModel.findById(id).exec()
|
|
28
|
+
return tenant
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async findByName(name: string): Promise<ITenant | null>{
|
|
32
|
+
const tenant: mongoose.HydratedDocument<ITenant> | null = await TenantModel.findOne({name}).exec()
|
|
33
|
+
return tenant
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async fetchAll(): Promise<ITenant[]>{
|
|
37
|
+
const tenants: mongoose.HydratedDocument<ITenant>[] = await TenantModel.find().exec()
|
|
38
|
+
return tenants
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async paginate(page:number = 1, limit:number = 5, search:string): Promise<IPaginateResult>{
|
|
42
|
+
|
|
43
|
+
const query = {}
|
|
44
|
+
|
|
45
|
+
if(search){
|
|
46
|
+
query['$or'] = [
|
|
47
|
+
{name: new RegExp(search, 'i')},
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const options = {page, limit} as PaginateOptions
|
|
52
|
+
const tenants: PaginateResult<ITenant> = await TenantModel.paginate(query, options)
|
|
53
|
+
return {
|
|
54
|
+
page: page,
|
|
55
|
+
limit: limit,
|
|
56
|
+
total: tenants.totalDocs,
|
|
57
|
+
items: tenants.docs
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default TenantMongoRepository
|
|
@@ -24,7 +24,7 @@ class UserMongoRepository implements IUserRepository {
|
|
|
24
24
|
|
|
25
25
|
const user: mongoose.HydratedDocument<IUser> = new UserModel(userData)
|
|
26
26
|
await user.save()
|
|
27
|
-
await user.populate('role')
|
|
27
|
+
await user.populate(['role','tenant'])
|
|
28
28
|
return user
|
|
29
29
|
}catch (e){
|
|
30
30
|
if(e instanceof mongoose.Error.ValidationError){
|
|
@@ -37,7 +37,7 @@ class UserMongoRepository implements IUserRepository {
|
|
|
37
37
|
|
|
38
38
|
async update(id: mongoose.Types.ObjectId, userData: IUserUpdate): Promise<IUser> {
|
|
39
39
|
try{
|
|
40
|
-
const user: mongoose.HydratedDocument<IUser> = await UserModel.findOneAndUpdate({_id: id}, userData, {new: true}).populate('role').exec()
|
|
40
|
+
const user: mongoose.HydratedDocument<IUser> = await UserModel.findOneAndUpdate({_id: id}, userData, {new: true}).populate(['role','tenant']).exec()
|
|
41
41
|
return user
|
|
42
42
|
}catch (e){
|
|
43
43
|
if(e instanceof mongoose.Error.ValidationError){
|
|
@@ -57,16 +57,16 @@ class UserMongoRepository implements IUserRepository {
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
async findById(id: mongoose.Types.ObjectId): Promise<IUser> {
|
|
60
|
-
const user: mongoose.HydratedDocument<IUser> = await UserModel.findById(id).populate('role').exec()
|
|
60
|
+
const user: mongoose.HydratedDocument<IUser> = await UserModel.findById(id).populate(['role','tenant']).exec()
|
|
61
61
|
return user
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
async findByUsername(username: string): Promise<IUser> {
|
|
65
|
-
const user: mongoose.HydratedDocument<IUser> = await UserModel.findOne({username: username}).populate('role').exec()
|
|
65
|
+
const user: mongoose.HydratedDocument<IUser> = await UserModel.findOne({username: username}).populate(['role','tenant']).exec()
|
|
66
66
|
return user
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
async paginate(page: number = 1, limit: number = 5, search?:string): Promise<IPaginateResult> {
|
|
69
|
+
async paginate(page: number = 1, limit: number = 5, search?:string, filters?:IPaginateFilter[]): Promise<IPaginateResult> {
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
const query = {}
|
|
@@ -79,7 +79,21 @@ class UserMongoRepository implements IUserRepository {
|
|
|
79
79
|
]
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
if(filters){
|
|
83
|
+
for(const filter of filters){
|
|
84
|
+
if(filter.operator === '$eq'){
|
|
85
|
+
query[filter.field] = {$eq: filter.value}
|
|
86
|
+
}
|
|
87
|
+
if(filter.operator === '$ne'){
|
|
88
|
+
query[filter.field] = {$ne: filter.value}
|
|
89
|
+
}
|
|
90
|
+
if(filter.operator === '$in'){
|
|
91
|
+
query[filter.field] = {$in: filter.value}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const options = {populate: ['role','tenant'], page: page, limit: limit }
|
|
83
97
|
|
|
84
98
|
const userPaginated: PaginateResult<IUser> = await UserModel.paginate(query, options)
|
|
85
99
|
return {
|
|
@@ -25,6 +25,7 @@ class RoleSqliteRepository implements IRoleRepository{
|
|
|
25
25
|
|
|
26
26
|
constructor(DATABASE:string, verbose:boolean = false) {
|
|
27
27
|
this.db = new sqlite(DATABASE, {verbose: verbose ? console.log : null});
|
|
28
|
+
this.table()
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
table() {
|
|
@@ -78,7 +79,7 @@ class RoleSqliteRepository implements IRoleRepository{
|
|
|
78
79
|
async findById(id: IID): Promise<IRole | null>{
|
|
79
80
|
const role = this.db.prepare('SELECT * FROM roles WHERE id = ?').get(id);
|
|
80
81
|
if(role){
|
|
81
|
-
|
|
82
|
+
await this.populateRole(role)
|
|
82
83
|
return role
|
|
83
84
|
}
|
|
84
85
|
return undefined
|
|
@@ -87,7 +88,7 @@ class RoleSqliteRepository implements IRoleRepository{
|
|
|
87
88
|
async findByName(name: string): Promise<IRole | null>{
|
|
88
89
|
const role = this.db.prepare('SELECT * FROM roles WHERE name = ?').get(name);
|
|
89
90
|
if(role){
|
|
90
|
-
|
|
91
|
+
await this.populateRole(role)
|
|
91
92
|
return role
|
|
92
93
|
}
|
|
93
94
|
return undefined
|
|
@@ -125,7 +126,7 @@ class RoleSqliteRepository implements IRoleRepository{
|
|
|
125
126
|
async fetchAll(): Promise<IRole[]>{
|
|
126
127
|
const roles = this.db.prepare('SELECT * FROM roles').all();
|
|
127
128
|
for (const role of roles) {
|
|
128
|
-
|
|
129
|
+
await this.populateRole(role)
|
|
129
130
|
}
|
|
130
131
|
return roles
|
|
131
132
|
}
|
|
@@ -133,7 +134,7 @@ class RoleSqliteRepository implements IRoleRepository{
|
|
|
133
134
|
async paginate(page = 1, limit = 5, search=""): Promise<IPaginateResult>{
|
|
134
135
|
const offset = page > 1 ? (page - 1) * limit : 0
|
|
135
136
|
|
|
136
|
-
let where
|
|
137
|
+
let where=""
|
|
137
138
|
if (search) {
|
|
138
139
|
where = ` WHERE name LIKE '%${search}%'`
|
|
139
140
|
}
|
|
@@ -142,10 +143,9 @@ class RoleSqliteRepository implements IRoleRepository{
|
|
|
142
143
|
const roles = this.db.prepare('SELECT * FROM roles LIMIT ? OFFSET ?'+where).all([limit, offset]);
|
|
143
144
|
|
|
144
145
|
for (const role of roles) {
|
|
145
|
-
|
|
146
|
+
await this.populateRole(role)
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
|
|
149
149
|
return {
|
|
150
150
|
page: page,
|
|
151
151
|
limit: limit,
|
|
@@ -154,6 +154,27 @@ class RoleSqliteRepository implements IRoleRepository{
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
async findWithoutPopulateById(id: IID): Promise<IRole | null>{
|
|
158
|
+
const role = this.db.prepare('SELECT * FROM roles WHERE id = ?').get(id);
|
|
159
|
+
if(role){
|
|
160
|
+
return role
|
|
161
|
+
}
|
|
162
|
+
return undefined
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async populateRole(role){
|
|
166
|
+
role.permissions = role.permissions? role.permissions.split(",") : []
|
|
167
|
+
role.childRoles = role.childRoles? role.childRoles.split(",") : []
|
|
168
|
+
|
|
169
|
+
const childRoles = []
|
|
170
|
+
for(const childRoleId of role.childRoles){
|
|
171
|
+
const childRole:IRole = await this.findWithoutPopulateById(childRoleId)
|
|
172
|
+
childRoles.push(childRole)
|
|
173
|
+
}
|
|
174
|
+
role.childRoles = childRoles
|
|
175
|
+
return role
|
|
176
|
+
}
|
|
177
|
+
|
|
157
178
|
|
|
158
179
|
}
|
|
159
180
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {ITenant} from '../../interfaces/ITenant'
|
|
2
|
+
import {ITenantRepository} from '../../interfaces/ITenantRepository'
|
|
3
|
+
import {UUID} from "crypto";
|
|
4
|
+
import sqlite from "better-sqlite3";
|
|
5
|
+
import {randomUUID} from "node:crypto";
|
|
6
|
+
import {IPaginateFilter, IPaginateResult, ValidationError} from "@drax/common-back";
|
|
7
|
+
import {SqliteErrorToValidationError} from "@drax/common-back";
|
|
8
|
+
import {IID} from "../../interfaces/IID";
|
|
9
|
+
|
|
10
|
+
const tenantTableSQL: string = `
|
|
11
|
+
CREATE TABLE IF NOT EXISTS tenants
|
|
12
|
+
(
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
name TEXT
|
|
15
|
+
);
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
class TenantSqliteRepository implements ITenantRepository{
|
|
19
|
+
|
|
20
|
+
private db: any;
|
|
21
|
+
|
|
22
|
+
constructor(DATABASE:string, verbose:boolean = false) {
|
|
23
|
+
this.db = new sqlite(DATABASE, {verbose: verbose ? console.log : null});
|
|
24
|
+
this.table()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
table() {
|
|
28
|
+
this.db.exec(tenantTableSQL);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async create(tenantData: ITenant): Promise<ITenant> {
|
|
34
|
+
try{
|
|
35
|
+
|
|
36
|
+
if(!tenantData.id){
|
|
37
|
+
tenantData.id = randomUUID()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
const fields = Object.keys(tenantData)
|
|
42
|
+
.map(field => `${field}`)
|
|
43
|
+
.join(', ');
|
|
44
|
+
|
|
45
|
+
const values = Object.keys(tenantData)
|
|
46
|
+
.map(field => `@${field}`)
|
|
47
|
+
.join(', ');
|
|
48
|
+
|
|
49
|
+
const stmt = this.db.prepare(`INSERT INTO tenants (${fields}) VALUES (${values})`);
|
|
50
|
+
stmt.run(tenantData)
|
|
51
|
+
return this.findById(tenantData.id as UUID)
|
|
52
|
+
}catch (e){
|
|
53
|
+
console.log(e)
|
|
54
|
+
throw SqliteErrorToValidationError(e, tenantData)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async findById(id: IID): Promise<ITenant | null>{
|
|
59
|
+
const tenant = this.db.prepare('SELECT * FROM tenants WHERE id = ?').get(id);
|
|
60
|
+
if(tenant){
|
|
61
|
+
tenant.permissions = tenant.permissions ? tenant.permissions.split(",") : []
|
|
62
|
+
return tenant
|
|
63
|
+
}
|
|
64
|
+
return undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async findByName(name: string): Promise<ITenant | null>{
|
|
68
|
+
const tenant = this.db.prepare('SELECT * FROM tenants WHERE name = ?').get(name);
|
|
69
|
+
if(tenant){
|
|
70
|
+
tenant.permissions = tenant.permissions ? tenant.permissions.split(",") : []
|
|
71
|
+
return tenant
|
|
72
|
+
}
|
|
73
|
+
return undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async update(id: IID, tenantData: ITenant): Promise<ITenant> {
|
|
77
|
+
try{
|
|
78
|
+
const setClauses = Object.keys(tenantData)
|
|
79
|
+
.map(field => `${field} = @${field}`)
|
|
80
|
+
.join(', ');
|
|
81
|
+
tenantData.id = id
|
|
82
|
+
const stmt = this.db.prepare( `UPDATE tenants SET ${setClauses} WHERE id = @id `);
|
|
83
|
+
stmt.run(tenantData);
|
|
84
|
+
return this.findById(id)
|
|
85
|
+
}catch (e){
|
|
86
|
+
console.log(e)
|
|
87
|
+
throw SqliteErrorToValidationError(e, tenantData)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async delete(id: IID): Promise<boolean> {
|
|
93
|
+
const stmt = this.db.prepare('DELETE FROM tenants WHERE id = ?');
|
|
94
|
+
stmt.run(id);
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async deleteAll(): Promise<boolean> {
|
|
99
|
+
const stmt = this.db.prepare('DELETE FROM tenants');
|
|
100
|
+
stmt.run();
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async fetchAll(): Promise<ITenant[]>{
|
|
105
|
+
const tenants = this.db.prepare('SELECT * FROM tenants').all();
|
|
106
|
+
for (const tenant of tenants) {
|
|
107
|
+
tenant.permissions = tenant.permissions? tenant.permissions.split(",") : []
|
|
108
|
+
}
|
|
109
|
+
return tenants
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async paginate(page = 1, limit = 5, search=""): Promise<IPaginateResult>{
|
|
113
|
+
const offset = page > 1 ? (page - 1) * limit : 0
|
|
114
|
+
|
|
115
|
+
let where=""
|
|
116
|
+
if (search) {
|
|
117
|
+
where = ` WHERE name LIKE '%${search}%'`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const rCount = this.db.prepare('SELECT COUNT(*) as count FROM tenants'+where).get();
|
|
121
|
+
const tenants = this.db.prepare('SELECT * FROM tenants LIMIT ? OFFSET ?'+where).all([limit, offset]);
|
|
122
|
+
|
|
123
|
+
for (const tenant of tenants) {
|
|
124
|
+
tenant.permissions = tenant.permissions? tenant.permissions.split(",") : []
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
page: page,
|
|
130
|
+
limit: limit,
|
|
131
|
+
total: rCount.count,
|
|
132
|
+
items: tenants
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default TenantSqliteRepository
|
|
@@ -3,50 +3,40 @@ import sqlite from "better-sqlite3";
|
|
|
3
3
|
import {randomUUID} from "node:crypto";
|
|
4
4
|
import {UUID} from "crypto";
|
|
5
5
|
import {IUserRepository} from "../../interfaces/IUserRepository";
|
|
6
|
-
import type {IPaginateResult} from "@drax/common-back";
|
|
7
|
-
import {
|
|
6
|
+
import type {IPaginateFilter, IPaginateResult} from "@drax/common-back";
|
|
7
|
+
import { SqliteErrorToValidationError, ValidationError} from "@drax/common-back";
|
|
8
8
|
import RoleSqliteRepository from "./RoleSqliteRepository.js";
|
|
9
|
+
import TenantSqliteRepository from "./TenantSqliteRepository.js";
|
|
9
10
|
import {IID} from "../../interfaces/IID";
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
const userTableSQL: string = `
|
|
13
14
|
CREATE TABLE IF NOT EXISTS users
|
|
14
15
|
(
|
|
15
|
-
id
|
|
16
|
-
TEXT
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
TEXT,
|
|
21
|
-
|
|
22
|
-
TEXT
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
password
|
|
27
|
-
TEXT,
|
|
28
|
-
email
|
|
29
|
-
TEXT
|
|
30
|
-
UNIQUE,
|
|
31
|
-
phone
|
|
32
|
-
TEXT,
|
|
33
|
-
role
|
|
34
|
-
TEXT,
|
|
35
|
-
groups
|
|
36
|
-
TEXT,
|
|
37
|
-
avatar
|
|
38
|
-
TEXT
|
|
16
|
+
id TEXT PRIMARY KEY,
|
|
17
|
+
name TEXT,
|
|
18
|
+
username TEXT UNIQUE,
|
|
19
|
+
active INTEGER,
|
|
20
|
+
password TEXT,
|
|
21
|
+
email TEXT UNIQUE,
|
|
22
|
+
phone TEXT,
|
|
23
|
+
role TEXT,
|
|
24
|
+
tenant TEXT,
|
|
25
|
+
groups TEXT,
|
|
26
|
+
avatar TEXT
|
|
39
27
|
);
|
|
40
28
|
`;
|
|
41
29
|
|
|
42
30
|
class UserSqliteRepository implements IUserRepository {
|
|
43
31
|
private db: any;
|
|
44
32
|
private roleRepository: RoleSqliteRepository;
|
|
33
|
+
private tenantRepository: TenantSqliteRepository;
|
|
45
34
|
|
|
46
35
|
constructor(DATABASE: string, verbose: boolean = false) {
|
|
47
36
|
this.db = new sqlite(DATABASE, {verbose: verbose ? console.log : null});
|
|
48
37
|
this.roleRepository = new RoleSqliteRepository(DATABASE, verbose)
|
|
49
|
-
|
|
38
|
+
this.tenantRepository = new TenantSqliteRepository(DATABASE, verbose)
|
|
39
|
+
this.table()
|
|
50
40
|
}
|
|
51
41
|
|
|
52
42
|
table() {
|
|
@@ -81,10 +71,6 @@ class UserSqliteRepository implements IUserRepository {
|
|
|
81
71
|
.map(field => `@${field}`)
|
|
82
72
|
.join(', ');
|
|
83
73
|
|
|
84
|
-
/*console.log("fields", fields)
|
|
85
|
-
console.log("values",values)
|
|
86
|
-
console.log("userData",userData)*/
|
|
87
|
-
|
|
88
74
|
const stmt = this.db.prepare(`INSERT INTO users (${fields})
|
|
89
75
|
VALUES (${values})`);
|
|
90
76
|
stmt.run(userData)
|
|
@@ -135,6 +121,7 @@ class UserSqliteRepository implements IUserRepository {
|
|
|
135
121
|
return null
|
|
136
122
|
}
|
|
137
123
|
user.role = await this.findRoleById(user.role)
|
|
124
|
+
user.tenant = await this.findTenantById(user.tenant)
|
|
138
125
|
return user
|
|
139
126
|
}
|
|
140
127
|
|
|
@@ -144,28 +131,49 @@ class UserSqliteRepository implements IUserRepository {
|
|
|
144
131
|
return null
|
|
145
132
|
}
|
|
146
133
|
user.role = await this.findRoleById(user.role)
|
|
134
|
+
user.tenant = await this.findTenantById(user.tenant)
|
|
147
135
|
return user
|
|
148
136
|
}
|
|
149
137
|
|
|
150
|
-
async paginate(page: number = 1, limit: number = 5, search
|
|
138
|
+
async paginate(page: number = 1, limit: number = 5, search: string = "", filters:IPaginateFilter[] = []): Promise<IPaginateResult> {
|
|
151
139
|
|
|
152
140
|
const offset = page > 1 ? (page - 1) * limit : 0
|
|
153
141
|
|
|
154
|
-
let where
|
|
142
|
+
let where=""
|
|
155
143
|
if (search) {
|
|
156
|
-
where = ` WHERE name LIKE '%${search}%' OR username LIKE '%${search}%'`
|
|
144
|
+
where = ` WHERE (name LIKE '%${search}%' OR username LIKE '%${search}%') `
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let whereFilters= []
|
|
148
|
+
if(filters && filters.length > 0 ){
|
|
149
|
+
where = where ? ` AND ` : ` WHERE `
|
|
150
|
+
for(const filter of filters){
|
|
151
|
+
if(filter.operator === '$eq'){
|
|
152
|
+
whereFilters.push(` ${filter.field} = '${filter.value}' `)
|
|
153
|
+
}
|
|
154
|
+
if(filter.operator === '$ne'){
|
|
155
|
+
whereFilters.push(` ${filter.field} != '${filter.value}' `)
|
|
156
|
+
}
|
|
157
|
+
if(filter.operator === '$in'){
|
|
158
|
+
whereFilters.push(` ${filter.field} LIKE '%${filter.value}%' `)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
where += whereFilters.join(" AND ")
|
|
157
162
|
}
|
|
158
163
|
|
|
164
|
+
// console.log("paginate where ", where, "search", search, "filters", filters, "whereFilters", whereFilters)
|
|
165
|
+
|
|
159
166
|
const rCount = this.db.prepare('SELECT COUNT(*) as count FROM users' + where).get();
|
|
160
|
-
|
|
167
|
+
|
|
168
|
+
const users = this.db.prepare('SELECT * FROM users' + where + ' LIMIT ? OFFSET ?').all([limit, offset]);
|
|
161
169
|
|
|
162
170
|
for (const user of users) {
|
|
171
|
+
|
|
163
172
|
let role = await this.findRoleById(user.role)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
173
|
+
user.role = role ? role : null
|
|
174
|
+
|
|
175
|
+
let tenant = await this.findTenantById(user.tenant)
|
|
176
|
+
user.tenant = tenant ? tenant : null
|
|
169
177
|
|
|
170
178
|
user.active = user.active === 1
|
|
171
179
|
}
|
|
@@ -182,6 +190,10 @@ class UserSqliteRepository implements IUserRepository {
|
|
|
182
190
|
return await this.roleRepository.findById(id)
|
|
183
191
|
}
|
|
184
192
|
|
|
193
|
+
async findTenantById(id: IID) {
|
|
194
|
+
return await this.tenantRepository.findById(id)
|
|
195
|
+
}
|
|
196
|
+
|
|
185
197
|
async changePassword(id: IID, password: string): Promise<boolean> {
|
|
186
198
|
const stmt = this.db.prepare(`UPDATE users
|
|
187
199
|
SET password = @password
|
package/src/routes/RoleRoutes.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {IdentityPermissions} from "../permissions/IdentityPermissions.js";
|
|
|
5
5
|
import {PermissionService} from "../services/PermissionService.js";
|
|
6
6
|
import UnauthorizedError from "../errors/UnauthorizedError.js";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
|
|
10
10
|
async function RoleRoutes(fastify, options) {
|
|
11
11
|
|
|
@@ -30,6 +30,7 @@ async function RoleRoutes(fastify, options) {
|
|
|
30
30
|
try {
|
|
31
31
|
request.rbac.assertPermission(IdentityPermissions.ViewRole)
|
|
32
32
|
const id = request.params.id
|
|
33
|
+
const roleService = RoleServiceFactory()
|
|
33
34
|
let role = await roleService.findById(id)
|
|
34
35
|
return role
|
|
35
36
|
} catch (e) {
|
|
@@ -51,6 +52,7 @@ async function RoleRoutes(fastify, options) {
|
|
|
51
52
|
try {
|
|
52
53
|
request.rbac.assertPermission(IdentityPermissions.ViewRole)
|
|
53
54
|
const name = request.params.name
|
|
55
|
+
const roleService = RoleServiceFactory()
|
|
54
56
|
let role = await roleService.findByName(name)
|
|
55
57
|
return role
|
|
56
58
|
} catch (e) {
|
|
@@ -71,8 +73,13 @@ async function RoleRoutes(fastify, options) {
|
|
|
71
73
|
fastify.get('/api/roles/all', async (request, reply): Promise<IRole[]> => {
|
|
72
74
|
try {
|
|
73
75
|
request.rbac.assertPermission(IdentityPermissions.ViewRole)
|
|
76
|
+
const roleService = RoleServiceFactory()
|
|
74
77
|
let roles = await roleService.fetchAll()
|
|
75
|
-
|
|
78
|
+
if(request.rbac.getRole?.childRoles?.length > 0) {
|
|
79
|
+
return roles.filter(role => request.rbac.getRole.childRoles.some(childRole => childRole.id === role.id));
|
|
80
|
+
}else{
|
|
81
|
+
return roles
|
|
82
|
+
}
|
|
76
83
|
} catch (e) {
|
|
77
84
|
console.error(e)
|
|
78
85
|
if (e instanceof ValidationError) {
|
|
@@ -94,6 +101,7 @@ async function RoleRoutes(fastify, options) {
|
|
|
94
101
|
const page = request.query.page
|
|
95
102
|
const limit = request.query.limit
|
|
96
103
|
const search = request.query.search
|
|
104
|
+
const roleService = RoleServiceFactory()
|
|
97
105
|
let paginateResult = await roleService.paginate(page, limit, search)
|
|
98
106
|
return paginateResult
|
|
99
107
|
} catch (e) {
|
|
@@ -115,6 +123,7 @@ async function RoleRoutes(fastify, options) {
|
|
|
115
123
|
try {
|
|
116
124
|
request.rbac.assertPermission(IdentityPermissions.CreateRole)
|
|
117
125
|
const payload = request.body
|
|
126
|
+
const roleService = RoleServiceFactory()
|
|
118
127
|
let role = await roleService.create(payload)
|
|
119
128
|
return role
|
|
120
129
|
} catch (e) {
|
|
@@ -138,7 +147,7 @@ async function RoleRoutes(fastify, options) {
|
|
|
138
147
|
request.rbac.assertPermission(IdentityPermissions.UpdateRole)
|
|
139
148
|
const id = request.params.id
|
|
140
149
|
const payload = request.body
|
|
141
|
-
|
|
150
|
+
const roleService = RoleServiceFactory()
|
|
142
151
|
const currentRole = await roleService.findById(id)
|
|
143
152
|
if(currentRole.readonly){
|
|
144
153
|
throw new ValidationError([{field:'name', reason:"role.readonly", value:payload.name}])
|
|
@@ -166,6 +175,11 @@ async function RoleRoutes(fastify, options) {
|
|
|
166
175
|
try {
|
|
167
176
|
request.rbac.assertPermission(IdentityPermissions.DeleteRole)
|
|
168
177
|
const id = request.params.id
|
|
178
|
+
const roleService = RoleServiceFactory()
|
|
179
|
+
const currentRole = await roleService.findById(id)
|
|
180
|
+
if(currentRole.readonly){
|
|
181
|
+
throw new UnauthorizedError()
|
|
182
|
+
}
|
|
169
183
|
let r = await roleService.delete(id)
|
|
170
184
|
return r
|
|
171
185
|
} catch (e) {
|