Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
H
hrsystem
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
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
Administrator
hrsystem
Commits
0b0931ce
Commit
0b0931ce
authored
Apr 01, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 18 files via Son of Anton
parent
f3429de4
Changes
18
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
1678 additions
and
0 deletions
+1678
-0
app.module.ts
backend/src/app.module.ts
+9
-0
api-keys.controller.ts
backend/src/modules/api-keys/api-keys.controller.ts
+65
-0
api-keys.module.ts
backend/src/modules/api-keys/api-keys.module.ts
+10
-0
api-keys.service.ts
backend/src/modules/api-keys/api-keys.service.ts
+247
-0
create-api-key.dto.ts
backend/src/modules/api-keys/dto/create-api-key.dto.ts
+28
-0
update-api-key.dto.ts
backend/src/modules/api-keys/dto/update-api-key.dto.ts
+29
-0
search-query.dto.ts
backend/src/modules/search/dto/search-query.dto.ts
+20
-0
search-response.dto.ts
backend/src/modules/search/dto/search-response.dto.ts
+16
-0
search.controller.ts
backend/src/modules/search/search.controller.ts
+22
-0
search.module.ts
backend/src/modules/search/search.module.ts
+10
-0
search.service.ts
backend/src/modules/search/search.service.ts
+453
-0
create-webhook.dto.ts
backend/src/modules/webhooks/dto/create-webhook.dto.ts
+24
-0
update-webhook.dto.ts
backend/src/modules/webhooks/dto/update-webhook.dto.ts
+26
-0
webhook-filter.dto.ts
backend/src/modules/webhooks/dto/webhook-filter.dto.ts
+10
-0
webhooks.controller.ts
backend/src/modules/webhooks/webhooks.controller.ts
+101
-0
webhooks.module.ts
backend/src/modules/webhooks/webhooks.module.ts
+10
-0
webhooks.service.ts
backend/src/modules/webhooks/webhooks.service.ts
+553
-0
schema-api-integration.prisma
prisma/schema-api-integration.prisma
+45
-0
No files found.
backend/src/app.module.ts
View file @
0b0931ce
...
@@ -58,6 +58,11 @@ import { ReportsModule } from './modules/reports/reports.module';
...
@@ -58,6 +58,11 @@ import { ReportsModule } from './modules/reports/reports.module';
// ─── Phase 3A: Admin & Intelligence ─────────────────────────
// ─── Phase 3A: Admin & Intelligence ─────────────────────────
import
{
AnalyticsModule
}
from
'./modules/analytics/analytics.module'
;
import
{
AnalyticsModule
}
from
'./modules/analytics/analytics.module'
;
// ─── Phase 3B: API & Integration ────────────────────────────
import
{
ApiKeysModule
}
from
'./modules/api-keys/api-keys.module'
;
import
{
WebhooksModule
}
from
'./modules/webhooks/webhooks.module'
;
import
{
SearchModule
}
from
'./modules/search/search.module'
;
import
{
JwtAuthGuard
}
from
'./common/guards/jwt-auth.guard'
;
import
{
JwtAuthGuard
}
from
'./common/guards/jwt-auth.guard'
;
import
{
RolesGuard
}
from
'./common/guards/roles.guard'
;
import
{
RolesGuard
}
from
'./common/guards/roles.guard'
;
import
{
TransformInterceptor
}
from
'./common/interceptors/transform.interceptor'
;
import
{
TransformInterceptor
}
from
'./common/interceptors/transform.interceptor'
;
...
@@ -113,6 +118,10 @@ import { RateLimitMiddleware } from './common/middleware/rate-limit.middleware';
...
@@ -113,6 +118,10 @@ import { RateLimitMiddleware } from './common/middleware/rate-limit.middleware';
ReportsModule
,
ReportsModule
,
// Phase 3A
// Phase 3A
AnalyticsModule
,
AnalyticsModule
,
// Phase 3B
ApiKeysModule
,
WebhooksModule
,
SearchModule
,
],
],
providers
:
[
providers
:
[
{
provide
:
APP_GUARD
,
useClass
:
JwtAuthGuard
},
{
provide
:
APP_GUARD
,
useClass
:
JwtAuthGuard
},
...
...
backend/src/modules/api-keys/api-keys.controller.ts
0 → 100644
View file @
0b0931ce
import
{
Controller
,
Get
,
Post
,
Put
,
Delete
,
Body
,
Param
,
HttpCode
,
HttpStatus
,
}
from
'@nestjs/common'
;
import
{
ApiKeysService
}
from
'./api-keys.service'
;
import
{
CreateApiKeyDto
}
from
'./dto/create-api-key.dto'
;
import
{
UpdateApiKeyDto
}
from
'./dto/update-api-key.dto'
;
import
{
CurrentUser
,
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
{
Roles
}
from
'../../common/decorators/roles.decorator'
;
@
Controller
(
'api-keys'
)
@
Roles
(
'SUPER_ADMIN'
)
export
class
ApiKeysController
{
constructor
(
private
readonly
apiKeysService
:
ApiKeysService
)
{}
@
Post
()
async
create
(@
Body
()
dto
:
CreateApiKeyDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
apiKeysService
.
create
(
dto
,
user
);
}
@
Get
()
async
findAll
(@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
apiKeysService
.
findAll
(
user
);
}
@
Get
(
':id'
)
async
findById
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
apiKeysService
.
findById
(
id
,
user
);
}
@
Put
(
':id'
)
async
update
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
UpdateApiKeyDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
apiKeysService
.
update
(
id
,
dto
,
user
);
}
@
Post
(
':id/revoke'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
revoke
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
apiKeysService
.
revoke
(
id
,
user
);
}
@
Post
(
':id/reactivate'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
reactivate
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
apiKeysService
.
reactivate
(
id
,
user
);
}
@
Delete
(
':id'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
delete
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
await
this
.
apiKeysService
.
delete
(
id
,
user
);
return
{
message
:
'API key permanently deleted'
};
}
}
\ No newline at end of file
backend/src/modules/api-keys/api-keys.module.ts
0 → 100644
View file @
0b0931ce
import
{
Module
}
from
'@nestjs/common'
;
import
{
ApiKeysController
}
from
'./api-keys.controller'
;
import
{
ApiKeysService
}
from
'./api-keys.service'
;
@
Module
({
controllers
:
[
ApiKeysController
],
providers
:
[
ApiKeysService
],
exports
:
[
ApiKeysService
],
})
export
class
ApiKeysModule
{}
\ No newline at end of file
backend/src/modules/api-keys/api-keys.service.ts
0 → 100644
View file @
0b0931ce
import
{
Injectable
,
NotFoundException
,
ForbiddenException
,
BadRequestException
,
Logger
,
}
from
'@nestjs/common'
;
import
{
PrismaService
}
from
'../../prisma/prisma.service'
;
import
{
CreateApiKeyDto
}
from
'./dto/create-api-key.dto'
;
import
{
UpdateApiKeyDto
}
from
'./dto/update-api-key.dto'
;
import
{
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
*
as
crypto
from
'crypto'
;
@
Injectable
()
export
class
ApiKeysService
{
private
readonly
logger
=
new
Logger
(
ApiKeysService
.
name
);
constructor
(
private
readonly
prisma
:
PrismaService
)
{}
async
create
(
dto
:
CreateApiKeyDto
,
currentUser
:
RequestUser
):
Promise
<
any
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin can manage API keys'
);
}
const
validScopes
=
[
'READ_ONLY'
,
'READ_WRITE'
,
'ADMIN'
];
if
(
!
validScopes
.
includes
(
dto
.
scope
))
{
throw
new
BadRequestException
(
`Scope must be one of:
${
validScopes
.
join
(
', '
)}
`
);
}
// Generate a cryptographically secure API key
const
rawKey
=
`grind_
${
dto
.
scope
.
toLowerCase
()}
_
${
crypto
.
randomBytes
(
32
).
toString
(
'hex'
)}
`
;
const
keyHash
=
crypto
.
createHash
(
'sha256'
).
update
(
rawKey
).
digest
(
'hex'
);
// Store only the first 8 chars as prefix for identification
const
keyPrefix
=
rawKey
.
substring
(
0
,
16
);
const
expiresAt
=
dto
.
expiresInDays
?
new
Date
(
Date
.
now
()
+
dto
.
expiresInDays
*
24
*
60
*
60
*
1000
)
:
null
;
const
apiKey
=
await
this
.
prisma
.
apiKey
.
create
({
data
:
{
name
:
dto
.
name
,
keyHash
,
keyPrefix
,
scope
:
dto
.
scope
,
description
:
dto
.
description
||
null
,
expiresAt
,
isActive
:
true
,
createdById
:
currentUser
.
id
,
rateLimit
:
dto
.
rateLimit
||
1000
,
// requests per hour
},
});
this
.
logger
.
log
(
`API key "
${
dto
.
name
}
" created by
${
currentUser
.
email
}
— scope:
${
dto
.
scope
}
`
,
);
// Return the raw key ONCE — it can never be retrieved again
return
{
id
:
apiKey
.
id
,
name
:
apiKey
.
name
,
key
:
rawKey
,
// ONLY time the full key is shown
keyPrefix
:
apiKey
.
keyPrefix
,
scope
:
apiKey
.
scope
,
description
:
apiKey
.
description
,
expiresAt
:
apiKey
.
expiresAt
,
rateLimit
:
apiKey
.
rateLimit
,
isActive
:
apiKey
.
isActive
,
createdAt
:
apiKey
.
createdAt
,
warning
:
'Store this key securely. It will NOT be shown again.'
,
};
}
async
findAll
(
currentUser
:
RequestUser
):
Promise
<
any
[]
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin can manage API keys'
);
}
const
keys
=
await
this
.
prisma
.
apiKey
.
findMany
({
orderBy
:
{
createdAt
:
'desc'
},
include
:
{
createdBy
:
{
select
:
{
id
:
true
,
firstName
:
true
,
lastName
:
true
}
},
},
});
return
keys
.
map
((
k
)
=>
({
id
:
k
.
id
,
name
:
k
.
name
,
keyPrefix
:
k
.
keyPrefix
,
scope
:
k
.
scope
,
description
:
k
.
description
,
isActive
:
k
.
isActive
,
expiresAt
:
k
.
expiresAt
,
isExpired
:
k
.
expiresAt
?
new
Date
()
>
k
.
expiresAt
:
false
,
rateLimit
:
k
.
rateLimit
,
lastUsedAt
:
k
.
lastUsedAt
,
createdBy
:
k
.
createdBy
,
createdAt
:
k
.
createdAt
,
}));
}
async
findById
(
id
:
string
,
currentUser
:
RequestUser
):
Promise
<
any
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin can manage API keys'
);
}
const
key
=
await
this
.
prisma
.
apiKey
.
findUnique
({
where
:
{
id
},
include
:
{
createdBy
:
{
select
:
{
id
:
true
,
firstName
:
true
,
lastName
:
true
}
},
},
});
if
(
!
key
)
throw
new
NotFoundException
(
'API key not found'
);
return
{
id
:
key
.
id
,
name
:
key
.
name
,
keyPrefix
:
key
.
keyPrefix
,
scope
:
key
.
scope
,
description
:
key
.
description
,
isActive
:
key
.
isActive
,
expiresAt
:
key
.
expiresAt
,
isExpired
:
key
.
expiresAt
?
new
Date
()
>
key
.
expiresAt
:
false
,
rateLimit
:
key
.
rateLimit
,
lastUsedAt
:
key
.
lastUsedAt
,
createdBy
:
key
.
createdBy
,
createdAt
:
key
.
createdAt
,
updatedAt
:
key
.
updatedAt
,
};
}
async
update
(
id
:
string
,
dto
:
UpdateApiKeyDto
,
currentUser
:
RequestUser
):
Promise
<
any
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin can manage API keys'
);
}
const
key
=
await
this
.
prisma
.
apiKey
.
findUnique
({
where
:
{
id
}
});
if
(
!
key
)
throw
new
NotFoundException
(
'API key not found'
);
const
updateData
:
any
=
{};
if
(
dto
.
name
!==
undefined
)
updateData
.
name
=
dto
.
name
;
if
(
dto
.
description
!==
undefined
)
updateData
.
description
=
dto
.
description
;
if
(
dto
.
rateLimit
!==
undefined
)
updateData
.
rateLimit
=
dto
.
rateLimit
;
if
(
dto
.
scope
!==
undefined
)
{
const
validScopes
=
[
'READ_ONLY'
,
'READ_WRITE'
,
'ADMIN'
];
if
(
!
validScopes
.
includes
(
dto
.
scope
))
{
throw
new
BadRequestException
(
`Scope must be one of:
${
validScopes
.
join
(
', '
)}
`
);
}
updateData
.
scope
=
dto
.
scope
;
}
if
(
dto
.
expiresInDays
!==
undefined
)
{
updateData
.
expiresAt
=
dto
.
expiresInDays
?
new
Date
(
Date
.
now
()
+
dto
.
expiresInDays
*
24
*
60
*
60
*
1000
)
:
null
;
}
const
updated
=
await
this
.
prisma
.
apiKey
.
update
({
where
:
{
id
},
data
:
updateData
,
});
this
.
logger
.
log
(
`API key "
${
updated
.
name
}
" updated by
${
currentUser
.
email
}
`
);
return
this
.
findById
(
id
,
currentUser
);
}
async
revoke
(
id
:
string
,
currentUser
:
RequestUser
):
Promise
<
any
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin can manage API keys'
);
}
const
key
=
await
this
.
prisma
.
apiKey
.
findUnique
({
where
:
{
id
}
});
if
(
!
key
)
throw
new
NotFoundException
(
'API key not found'
);
if
(
!
key
.
isActive
)
{
throw
new
BadRequestException
(
'API key is already revoked'
);
}
const
updated
=
await
this
.
prisma
.
apiKey
.
update
({
where
:
{
id
},
data
:
{
isActive
:
false
},
});
this
.
logger
.
log
(
`API key "
${
updated
.
name
}
" revoked by
${
currentUser
.
email
}
`
);
return
this
.
findById
(
id
,
currentUser
);
}
async
reactivate
(
id
:
string
,
currentUser
:
RequestUser
):
Promise
<
any
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin can manage API keys'
);
}
const
key
=
await
this
.
prisma
.
apiKey
.
findUnique
({
where
:
{
id
}
});
if
(
!
key
)
throw
new
NotFoundException
(
'API key not found'
);
if
(
key
.
isActive
)
{
throw
new
BadRequestException
(
'API key is already active'
);
}
const
updated
=
await
this
.
prisma
.
apiKey
.
update
({
where
:
{
id
},
data
:
{
isActive
:
true
},
});
this
.
logger
.
log
(
`API key "
${
updated
.
name
}
" reactivated by
${
currentUser
.
email
}
`
);
return
this
.
findById
(
id
,
currentUser
);
}
async
delete
(
id
:
string
,
currentUser
:
RequestUser
):
Promise
<
void
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin can manage API keys'
);
}
const
key
=
await
this
.
prisma
.
apiKey
.
findUnique
({
where
:
{
id
}
});
if
(
!
key
)
throw
new
NotFoundException
(
'API key not found'
);
await
this
.
prisma
.
apiKey
.
delete
({
where
:
{
id
}
});
this
.
logger
.
log
(
`API key "
${
key
.
name
}
" permanently deleted by
${
currentUser
.
email
}
`
);
}
async
validateKey
(
rawKey
:
string
):
Promise
<
{
valid
:
boolean
;
key
?:
any
}
>
{
const
keyHash
=
crypto
.
createHash
(
'sha256'
).
update
(
rawKey
).
digest
(
'hex'
);
const
key
=
await
this
.
prisma
.
apiKey
.
findFirst
({
where
:
{
keyHash
,
isActive
:
true
,
OR
:
[{
expiresAt
:
null
},
{
expiresAt
:
{
gt
:
new
Date
()
}
}],
},
});
if
(
!
key
)
return
{
valid
:
false
};
// Update last used timestamp
await
this
.
prisma
.
apiKey
.
update
({
where
:
{
id
:
key
.
id
},
data
:
{
lastUsedAt
:
new
Date
()
},
});
return
{
valid
:
true
,
key
};
}
}
\ No newline at end of file
backend/src/modules/api-keys/dto/create-api-key.dto.ts
0 → 100644
View file @
0b0931ce
import
{
IsString
,
IsOptional
,
IsInt
,
Min
,
Max
,
MinLength
,
MaxLength
}
from
'class-validator'
;
export
class
CreateApiKeyDto
{
@
IsString
()
@
MinLength
(
2
)
@
MaxLength
(
100
)
name
:
string
;
@
IsString
()
scope
:
string
;
// READ_ONLY, READ_WRITE, ADMIN
@
IsOptional
()
@
IsString
()
@
MaxLength
(
500
)
description
?:
string
;
@
IsOptional
()
@
IsInt
()
@
Min
(
1
)
@
Max
(
365
)
expiresInDays
?:
number
;
@
IsOptional
()
@
IsInt
()
@
Min
(
10
)
@
Max
(
100000
)
rateLimit
?:
number
;
// requests per hour
}
\ No newline at end of file
backend/src/modules/api-keys/dto/update-api-key.dto.ts
0 → 100644
View file @
0b0931ce
import
{
IsString
,
IsOptional
,
IsInt
,
Min
,
Max
,
MaxLength
}
from
'class-validator'
;
export
class
UpdateApiKeyDto
{
@
IsOptional
()
@
IsString
()
@
MaxLength
(
100
)
name
?:
string
;
@
IsOptional
()
@
IsString
()
scope
?:
string
;
@
IsOptional
()
@
IsString
()
@
MaxLength
(
500
)
description
?:
string
;
@
IsOptional
()
@
IsInt
()
@
Min
(
1
)
@
Max
(
365
)
expiresInDays
?:
number
;
@
IsOptional
()
@
IsInt
()
@
Min
(
10
)
@
Max
(
100000
)
rateLimit
?:
number
;
}
\ No newline at end of file
backend/src/modules/search/dto/search-query.dto.ts
0 → 100644
View file @
0b0931ce
import
{
IsString
,
IsOptional
,
IsInt
,
Min
,
Max
,
MinLength
}
from
'class-validator'
;
import
{
Type
}
from
'class-transformer'
;
export
class
SearchQueryDto
{
@
IsString
()
@
MinLength
(
2
,
{
message
:
'Search query must be at least 2 characters'
})
q
:
string
;
@
IsOptional
()
@
IsString
()
entityTypes
?:
string
;
// Comma-separated: CARDS,USERS,BOARDS,DEDUCTIONS,LABELS,MESSAGES,NOTIFICATIONS
@
IsOptional
()
@
Type
(()
=>
Number
)
@
IsInt
()
@
Min
(
1
)
@
Max
(
100
)
limit
?:
number
;
}
\ No newline at end of file
backend/src/modules/search/dto/search-response.dto.ts
0 → 100644
View file @
0b0931ce
export
class
SearchResultDto
{
type
:
string
;
id
:
string
;
title
:
string
;
subtitle
:
string
|
null
;
context
:
string
|
null
;
url
:
string
;
score
:
number
;
}
export
class
SearchResponseDto
{
query
:
string
;
totalResults
:
number
;
results
:
SearchResultDto
[];
groupedResults
:
Record
<
string
,
SearchResultDto
[]
>
;
}
\ No newline at end of file
backend/src/modules/search/search.controller.ts
0 → 100644
View file @
0b0931ce
import
{
Controller
,
Get
,
Query
}
from
'@nestjs/common'
;
import
{
SearchService
}
from
'./search.service'
;
import
{
SearchQueryDto
}
from
'./dto/search-query.dto'
;
import
{
CurrentUser
,
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
@
Controller
(
'search'
)
export
class
SearchController
{
constructor
(
private
readonly
searchService
:
SearchService
)
{}
@
Get
()
async
search
(@
Query
()
query
:
SearchQueryDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
searchService
.
search
(
query
,
user
);
}
@
Get
(
'quick'
)
async
quickSearch
(@
Query
(
'q'
)
q
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
if
(
!
q
||
q
.
trim
().
length
<
2
)
{
return
{
results
:
[]
};
}
return
this
.
searchService
.
quickSearch
(
q
.
trim
(),
user
);
}
}
\ No newline at end of file
backend/src/modules/search/search.module.ts
0 → 100644
View file @
0b0931ce
import
{
Module
}
from
'@nestjs/common'
;
import
{
SearchController
}
from
'./search.controller'
;
import
{
SearchService
}
from
'./search.service'
;
@
Module
({
controllers
:
[
SearchController
],
providers
:
[
SearchService
],
exports
:
[
SearchService
],
})
export
class
SearchModule
{}
\ No newline at end of file
backend/src/modules/search/search.service.ts
0 → 100644
View file @
0b0931ce
This diff is collapsed.
Click to expand it.
backend/src/modules/webhooks/dto/create-webhook.dto.ts
0 → 100644
View file @
0b0931ce
import
{
IsString
,
IsOptional
,
IsArray
,
MinLength
,
MaxLength
,
IsUrl
}
from
'class-validator'
;
export
class
CreateWebhookDto
{
@
IsString
()
@
MinLength
(
2
)
@
MaxLength
(
100
)
name
:
string
;
@
IsString
()
url
:
string
;
@
IsOptional
()
@
IsString
()
secret
?:
string
;
@
IsArray
()
@
IsString
({
each
:
true
})
events
:
string
[];
@
IsOptional
()
@
IsString
()
@
MaxLength
(
500
)
description
?:
string
;
}
\ No newline at end of file
backend/src/modules/webhooks/dto/update-webhook.dto.ts
0 → 100644
View file @
0b0931ce
import
{
IsString
,
IsOptional
,
IsArray
,
MaxLength
}
from
'class-validator'
;
export
class
UpdateWebhookDto
{
@
IsOptional
()
@
IsString
()
@
MaxLength
(
100
)
name
?:
string
;
@
IsOptional
()
@
IsString
()
url
?:
string
;
@
IsOptional
()
@
IsString
()
secret
?:
string
;
@
IsOptional
()
@
IsArray
()
@
IsString
({
each
:
true
})
events
?:
string
[];
@
IsOptional
()
@
IsString
()
@
MaxLength
(
500
)
description
?:
string
;
}
\ No newline at end of file
backend/src/modules/webhooks/dto/webhook-filter.dto.ts
0 → 100644
View file @
0b0931ce
import
{
IsOptional
,
IsBoolean
}
from
'class-validator'
;
import
{
Type
}
from
'class-transformer'
;
import
{
PaginationDto
}
from
'../../../common/dto/pagination.dto'
;
export
class
WebhookFilterDto
extends
PaginationDto
{
@
IsOptional
()
@
Type
(()
=>
Boolean
)
@
IsBoolean
()
isActive
?:
boolean
;
}
\ No newline at end of file
backend/src/modules/webhooks/webhooks.controller.ts
0 → 100644
View file @
0b0931ce
import
{
Controller
,
Get
,
Post
,
Put
,
Delete
,
Body
,
Param
,
Query
,
HttpCode
,
HttpStatus
,
}
from
'@nestjs/common'
;
import
{
WebhooksService
}
from
'./webhooks.service'
;
import
{
CreateWebhookDto
}
from
'./dto/create-webhook.dto'
;
import
{
UpdateWebhookDto
}
from
'./dto/update-webhook.dto'
;
import
{
WebhookFilterDto
}
from
'./dto/webhook-filter.dto'
;
import
{
CurrentUser
,
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
{
Roles
}
from
'../../common/decorators/roles.decorator'
;
@
Controller
(
'webhooks'
)
@
Roles
(
'SUPER_ADMIN'
)
export
class
WebhooksController
{
constructor
(
private
readonly
webhooksService
:
WebhooksService
)
{}
@
Post
()
async
create
(@
Body
()
dto
:
CreateWebhookDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
webhooksService
.
create
(
dto
,
user
);
}
@
Get
()
async
findAll
(@
Query
()
filter
:
WebhookFilterDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
webhooksService
.
findAll
(
filter
,
user
);
}
@
Get
(
'events'
)
async
getAvailableEvents
()
{
return
this
.
webhooksService
.
getAvailableEvents
();
}
@
Get
(
':id'
)
async
findById
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
webhooksService
.
findById
(
id
,
user
);
}
@
Get
(
':id/deliveries'
)
async
getDeliveries
(
@
Param
(
'id'
)
id
:
string
,
@
Query
(
'page'
)
page
?:
string
,
@
Query
(
'limit'
)
limit
?:
string
,
@
CurrentUser
()
user
?:
RequestUser
,
)
{
return
this
.
webhooksService
.
getDeliveries
(
id
,
page
?
parseInt
(
page
,
10
)
:
1
,
limit
?
parseInt
(
limit
,
10
)
:
50
,
);
}
@
Put
(
':id'
)
async
update
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
UpdateWebhookDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
webhooksService
.
update
(
id
,
dto
,
user
);
}
@
Post
(
':id/test'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
testWebhook
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
webhooksService
.
sendTestEvent
(
id
,
user
);
}
@
Post
(
':id/activate'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
activate
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
webhooksService
.
setActive
(
id
,
true
,
user
);
}
@
Post
(
':id/deactivate'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
deactivate
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
webhooksService
.
setActive
(
id
,
false
,
user
);
}
@
Delete
(
':id'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
delete
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
await
this
.
webhooksService
.
delete
(
id
,
user
);
return
{
message
:
'Webhook deleted'
};
}
@
Post
(
'deliveries/:deliveryId/retry'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
retryDelivery
(
@
Param
(
'deliveryId'
)
deliveryId
:
string
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
webhooksService
.
retryDelivery
(
deliveryId
,
user
);
}
}
\ No newline at end of file
backend/src/modules/webhooks/webhooks.module.ts
0 → 100644
View file @
0b0931ce
import
{
Module
}
from
'@nestjs/common'
;
import
{
WebhooksController
}
from
'./webhooks.controller'
;
import
{
WebhooksService
}
from
'./webhooks.service'
;
@
Module
({
controllers
:
[
WebhooksController
],
providers
:
[
WebhooksService
],
exports
:
[
WebhooksService
],
})
export
class
WebhooksModule
{}
\ No newline at end of file
backend/src/modules/webhooks/webhooks.service.ts
0 → 100644
View file @
0b0931ce
This diff is collapsed.
Click to expand it.
prisma/schema-api-integration.prisma
0 → 100644
View file @
0b0931ce
//
───
API
INTEGRATION
MODELS
─────────────────────────────────
//
Phase
3
B
:
API
Keys
,
Webhooks
,
Search
model
Webhook
{
id
String
@
id
@
default
(
uuid
())
name
String
url
String
secret
String
?
events
String
[]
//
Array
of
event
names
subscribed
to
isActive
Boolean
@
default
(
true
)
description
String
?
createdById
String
createdBy
User
@
relation
(
"WebhookCreator"
,
fields
:
[
createdById
],
references
:
[
id
],
onDelete
:
RESTRICT
)
deliveries
WebhookDelivery
[]
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
@@
index
([
isActive
])
@@
index
([
createdById
])
}
model
WebhookDelivery
{
id
String
@
id
@
default
(
uuid
())
webhookId
String
webhook
Webhook
@
relation
(
fields
:
[
webhookId
],
references
:
[
id
],
onDelete
:
CASCADE
)
event
String
payload
Json
status
String
@
default
(
"PENDING"
)
//
PENDING
,
DELIVERED
,
FAILED
,
PERMANENTLY_FAILED
,
CANCELLED
responseCode
Int
?
lastError
String
?
attempts
Int
@
default
(
0
)
nextRetryAt
DateTime
?
deliveredAt
DateTime
?
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
@@
index
([
webhookId
])
@@
index
([
status
])
@@
index
([
nextRetryAt
])
@@
index
([
createdAt
])
}
\ No newline at end of file
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