Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge current v4.4 into test for uat #17

Merged
merged 25 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b69955b
update dockerfile remove bulk comments and fix missing --ignore-scripts
JamieVangeysel Nov 28, 2024
3f43d87
use latest, merge run statements
JamieVangeysel Nov 28, 2024
bdf05d5
move build command into package.json
JamieVangeysel Nov 28, 2024
d41f714
create separate func for cheking deletedOn Date
JamieVangeysel Nov 28, 2024
56210c8
fix regexpr char
JamieVangeysel Nov 28, 2024
146d76a
fix dupes in regexpr
JamieVangeysel Nov 28, 2024
dd008a9
removed --chown=node:node
JamieVangeysel Nov 28, 2024
1ddf34b
fix requested changes
JamieVangeysel Nov 28, 2024
eec7439
simplify upload methods in upload
JamieVangeysel Nov 28, 2024
f6fcef2
add build for sonar
JamieVangeysel Nov 28, 2024
27db229
create sonar props
JamieVangeysel Nov 28, 2024
6e3681d
add test
JamieVangeysel Nov 28, 2024
0209058
update readme
JamieVangeysel Nov 28, 2024
c5efdf3
fix use *
JamieVangeysel Nov 28, 2024
25bf73c
skip coverage?
JamieVangeysel Nov 28, 2024
8d4af89
exclude all for coverage
JamieVangeysel Nov 28, 2024
6454a78
fix duplications by creating super function
JamieVangeysel Nov 28, 2024
eaab0ff
add image version declaration so updates wont brick builds
JamieVangeysel Nov 28, 2024
82901ee
use esbuild image
JamieVangeysel Dec 2, 2024
e978f73
add breadcrumbs to browse response
JamieVangeysel Dec 4, 2024
4da7de2
add breadcrumbs to document resp
JamieVangeysel Dec 4, 2024
40f5e9a
do ip_address check ina separate function
JamieVangeysel Dec 6, 2024
b9e20a0
fix Unexpected lexical declaration
JamieVangeysel Dec 9, 2024
dbcf5e6
test added breadcrumbs
JamieVangeysel Dec 9, 2024
28e2119
remove unused assign of browse_repo
JamieVangeysel Dec 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Build
on:
push:
branches:
- main # The default branch
- v4.* # The other version branches to be analyzed
- test # long-lived test branch
pull_request:
types: [opened, synchronize, reopened]
jobs:
sonarcloud:
name: SonarQube Cloud
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: SonarQube Cloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
39 changes: 11 additions & 28 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,47 +1,30 @@
# ---- Deps ----
FROM groupclaes/npm AS depedencies

# change the working directory to new exclusive app folder
# ---- deps ----
FROM groupclaes/esbuild:v0.24.0 AS deps
WORKDIR /usr/src/app

# copy package file
COPY package.json ./package.json
COPY .npmrc ./.npmrc

# install node packages
RUN npm install --omit=dev

# ---- Build ----
FROM depedencies AS build
RUN npm install --omit=dev --ignore-scripts

# copy project
# ---- build ----
FROM deps AS build
COPY index.ts ./index.ts
COPY src/ ./src

# install node packages
RUN npm install

# create esbuild package
RUN esbuild ./index.ts --bundle --platform=node --minify --packages=external --external:'./config' --outfile=index.min.js

# --- release ---
FROM groupclaes/node
RUN npm install --ignore-scripts && npm run build

# ---- final ----
FROM groupclaes/node:20
# add lib form pdf and image manipulation
USER root
RUN apk add --no-cache file imagemagick

# set current user to node
USER node

# change the working directory to new exclusive app folder
WORKDIR /usr/src/app

# copy dependencies
COPY --chown=node:node --from=depedencies /usr/src/app ./

# copy project file
COPY --chown=node:node --from=build /usr/src/app/index.min.js ./
# removed --chown=node:node
COPY --from=deps /usr/src/app ./
COPY --from=build /usr/src/app/index.min.js ./

# command to run when intantiate an image
CMD ["node","index.min.js"]
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
# Manage PCM api contains all controllers for the management ui available on pcm.groupclaes.be
# Management API for PCM [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=groupclaes_pcm-api-manage&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=groupclaes_pcm-api-manage) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=groupclaes_pcm-api-manage&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=groupclaes_pcm-api-manage) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=groupclaes_pcm-api-manage&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=groupclaes_pcm-api-manage) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=groupclaes_pcm-api-manage&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=groupclaes_pcm-api-manage) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=groupclaes_pcm-api-manage&metric=bugs)](https://sonarcloud.io/summary/new_code?id=groupclaes_pcm-api-manage) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=groupclaes_pcm-api-manage&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=groupclaes_pcm-api-manage)

added jose, check JWT token using JWKS, on ISSUER
This api contains all controllers for the management interface available on https://pcm.groupclaes.be/

## Available controllers & routes
- /access-log
- GET / #retrieve list of access known in DB
- /attributes
- GET / #retrieve list of access known in DB
- /browse
- GET /
- /check
- GET / #get a list of items and datasheet availablility for the provided supplier_id
- GET /search #query suppliers based on search parameters
- POST /export #generate export csv based on body send in request
- /directories
- GET / # list of directories known in DB
- GET /:id #retrieve details of request directory #id
- /document
-
- /languages
- GET / #retrieve list of languages known in DB
- /profile
- GET /dashboard #retrieve user's dashboard view; showing items last changed
- /search
- GET / #execute search query
- /upload
- POST / #upload content to PCM
- /users
- GET / #retrieve list of users known in DB
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"author": "Jamie Vangeysel",
"license": "MIT",
"scripts": {
"build": "esbuild ./index.ts --bundle --platform=node --minify --packages=external --external:'./config' --outfile=index.min.js",
"test": "tap --reporter=list --show-full-coverage"
},
"dependencies": {
Expand Down
15 changes: 15 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
sonar.projectKey=groupclaes_pcm-api-manage
sonar.organization=groupclaes
sonar.coverage.skip=true
sonar.coverage.exclusions=**/*

# This is the name and version displayed in the SonarCloud UI.
#sonar.projectName=pcm-api-manage
#sonar.projectVersion=1.0


# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
#sonar.sources=.

# Encoding of the source code. Default is default system encoding
#sonar.sourceEncoding=UTF-8
37 changes: 1 addition & 36 deletions src/controllers/browse.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ declare module 'fastify' {
export interface FastifyInstance {
getSqlPool: (name?: string) => Promise<sql.ConnectionPool>
}

export interface FastifyRequest {
jwt: JWTPayload
hasRole: (role: string) => boolean
Expand Down Expand Up @@ -61,39 +61,4 @@ export default async function (fastify: FastifyInstance) {
return reply.error('failed to get browse view!')
}
})

/**
* Get browse page content
* @route GET /{APP_VERSION}/manage/browse/breadcrumbs
*/
fastify.get('/breadcrumbs', async function (request: FastifyRequest<{
Querystring: {
directory: number
}
}>, reply: FastifyReply) {
const start = performance.now()

if (!request.jwt?.sub)
return reply.fail({ jwt: 'missing authorization' }, 401)

if (!request.hasPermission('read', 'GroupClaes.PCM/browse'))
return reply.fail({ role: 'missing permission' }, 403)

try {
const pool = await fastify.getSqlPool()
const repo = new Browse(request.log, pool)

const result = await repo.getBreadcrumbs(request.query.directory, request.jwt.sub)

if (result.verified) {
if (result.error) return reply.error(result.error)

return reply.success({ breadcrumbs: result.breadcrumbs })
}
return reply.error('Session has expired!', 401, performance.now() - start)
} catch (err) {
request.log.error({ err }, 'failed to get breadcrumbs!')
return reply.error('failed to get breadcrumbs!')
}
})
}
8 changes: 7 additions & 1 deletion src/controllers/document.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,13 @@ export default async function (fastify: FastifyInstance) {
}
}

return reply.success({ document: result.result }, 200, performance.now() - start)
let response: { document: any, breadcrumbs?: any } = {
document: result.result
}
if (result.breadcrumbs)
response.breadcrumbs = result.breadcrumbs

return reply.success(response, 200, performance.now() - start)
}

return reply.error('Session has expired!', 401, performance.now() - start)
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/search.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ declare module 'fastify' {

export default async function (fastify: FastifyInstance) {
/**
* Get all attribute entries from DB
* @route GET /{APP_VERSION}/manage/languages
* Search
* @route GET /{APP_VERSION}/manage/search
*/
fastify.get('', async function (request: FastifyRequest<{
Querystring: {
Expand Down
93 changes: 49 additions & 44 deletions src/controllers/upload.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const deleteOnDirs = [
231
]

import Document, { DBResultSet } from '../repositories/document.repository'
import Document, { DBResultSet, IPostedDocument } from '../repositories/document.repository'
import sql from 'mssql'
import { env } from 'process'

Expand Down Expand Up @@ -56,17 +56,7 @@ export default async function (fastify: FastifyInstance) {
const start = performance.now()

let error

request.log.info({ client_ip: request.headers['x-client-ip'] }, 'the client ip is')

const ip_address = request.headers['x-client-ip']?.toString().split(',')[0]
if (ip_address && ip_address === '172.18.15.9') {
request.jwt = {
sub: '0',
roles: ['admin:GroupClaes.PCM/*']
}
request.hasPermission = (r, s) => true
}
checkIfDevserver(request)

if (!request.jwt?.sub)
return reply.fail({ jwt: 'missing authorization' }, 401)
Expand Down Expand Up @@ -109,28 +99,15 @@ export default async function (fastify: FastifyInstance) {
fs.mkdirSync(`${env['DATA_PATH']}/content/${uuid.substring(0, 2)}/${uuid}`, { recursive: true })
await pump(data.file, fs.createWriteStream(_fn))

let dt: Date | undefined

if (deleteOnDirs.some(e => e == request.query.directory_id)) {
// try parse date
const fn = path.parse(data.filename).name
const last8 = fn.substring(fn.length - 8)
if (/^[0-9]{8}$/.test(last8.toLocaleLowerCase())) {
const year = parseInt(last8.substring(0, 4))
const month = parseInt(last8.substring(4, 6))
const day = parseInt(last8.substring(6, 8))

dt = new Date(Date.UTC(year, month - 1, day))
}
}

let mimetype = data.mimetype
let filename = data.filename
let filesize = data.file.bytesRead

const dt = deleteOnDate(request.query.directory_id, path.parse(filename).name)

// filesize = convertImage(mimetype, filename, _fn, uuid)
// check if the uploaded file is a jpg or png, if so convert the file to webp.
// converted files will have an extra file in directory for safekeeping: file_source
//if (request.jwt.sub === '1') {
if ([
'image/png',
'image/jpeg',
Expand All @@ -148,16 +125,6 @@ export default async function (fastify: FastifyInstance) {
})
.toBuffer()

// http://pcm.groupclaes.be/v4/i/mac/artikel/foto/ ?s=thumb_large&swp
// const is_smaller = buffer.length < filesize
// if (!is_smaller)
// buffer = await sharp(_ofn)
// .withMetadata()
// .webp({
// effort: 6
// })
// .toBuffer()

fs.writeFileSync(_fn, buffer)
// remove file_source
fs.unlinkSync(_ofn)
Expand Down Expand Up @@ -185,14 +152,24 @@ export default async function (fastify: FastifyInstance) {
// check if the file was not deleted
}
}
//}

const document: IPostedDocument = {
uuid,
directory_id: request.query.directory_id,
name: filename,
mime_type: mimetype,
size: filesize,
object_type,
document_type,
deleted_on: dt
}

if (!request.query.mode) {
results.push(await repo.create(uuid, request.query.directory_id, filename, mimetype, filesize, object_type, document_type, dt, request.jwt.sub))
results.push(await repo.create(undefined, document, request.jwt.sub))
} else if (request.query.id) {
switch (request.query.mode) {
case 'update':
const result = await repo.createUpdate(request.query.id, uuid, request.query.directory_id, filename, mimetype, filesize, object_type, document_type, dt, request.jwt.sub)
case 'update': {
const result = await repo.create(request.query.id, document, request.jwt.sub, 'Update')
if (result.result.length > 0) {
const _ouuid = result.result[0].guid.toLocaleLowerCase()
const _ofn = `${env['DATA_PATH']}/content/${_ouuid.substring(0, 2)}/${_ouuid}/file`
Expand All @@ -201,9 +178,9 @@ export default async function (fastify: FastifyInstance) {
}
results.push(result)
break

}
case 'version':
results.push(await repo.createVersion(request.query.id, uuid, request.query.directory_id, filename, mimetype, filesize, object_type, document_type, dt, request.jwt.sub))
results.push(await repo.create(request.query.id, document, request.jwt.sub, 'Version'))
break
}
}
Expand All @@ -220,4 +197,32 @@ export default async function (fastify: FastifyInstance) {
return reply.error('failed to upload document!')
}
})
}

function deleteOnDate(directory_id: number, filename: string): Date | undefined {
if (deleteOnDirs.some(e => e == directory_id)) {
// try parse date
const last8 = filename.substring(filename.length - 8)
if (/^\d{8}$/.test(last8.toLocaleLowerCase())) {
const year = parseInt(last8.substring(0, 4))
const month = parseInt(last8.substring(4, 6))
const day = parseInt(last8.substring(6, 8))

return new Date(Date.UTC(year, month - 1, day))
}
}
return undefined
}

function checkIfDevserver(request: FastifyRequest) {
const ip_address = request.headers['x-client-ip']?.toString().split(',')[0]
request.log.info({ client_ip: ip_address }, 'checkIfDevserver')

if (ip_address && ip_address === '172.18.15.9') {
request.jwt = {
sub: '0',
roles: ['admin:GroupClaes.PCM/*']
}
request.hasPermission = (r, s) => true
}
}
8 changes: 0 additions & 8 deletions src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,4 @@ const BitConverter = {
| src[index + 6] << 8
| src[index + 7]
}
}

export interface IBaseAPIResponse {
status: 'error' | 'success' | 'fail'
code?: number // HTTP status code
message?: string // Required when status is 'error'
data?: { [key: string]: any } | any // Required if status is 'success' or 'fail', data should never be an array
executionTime?: number // Optional: Execution time in milliseconds
}
3 changes: 2 additions & 1 deletion src/repositories/browse.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export default class Browse {
verified,
result: {
directories: result.recordsets[1][0] || [],
documents: result.recordsets[2][0] || []
documents: result.recordsets[2][0] || [],
breadcrumbs: result.recordsets[3][0] || []
}
}
} else {
Expand Down
Loading
Loading