33 Commits

Author SHA1 Message Date
CPATRD f890fce7c9 Bump version to 9.4.0 2026-04-09 14:14:04 +02:00
CPATRD d8b9fd7664 Added CPATapi.Server API Client 2026-04-09 14:14:04 +02:00
CPATRD 6d1d165aba Added OpenAPI 2026-04-09 14:14:04 +02:00
CPATRD 4c6342a989 Bump version to 9.3.1 2026-03-27 11:00:42 +01:00
CPATRD b47debeef2 svg loader 2026-03-27 11:00:10 +01:00
CPATRD 2886ab6522 Serilog 2026-03-19 14:32:20 +01:00
CPATRD 994cc72e1d Bump version to 9.3.0 2026-03-19 14:32:19 +01:00
CPATRD a02ce50cf3 Change favicon color depending on status 2026-03-19 14:32:19 +01:00
CPATRD c16c07ea42 New 3cx tapi server url 2026-03-19 14:32:19 +01:00
CPATRD 535cf6ea05 Removed unused imports 2026-03-19 14:32:19 +01:00
CPATRD 766ed86999 Modernize and Dockerize server 2026-03-19 14:32:14 +01:00
CPATRD 76a2bf0e88 Modernized 2026-03-19 13:42:42 +01:00
CPATRD fe8fcdf45b 3cx Tapi Server 2026-03-19 13:42:36 +01:00
CPATRD 7a99b1ab55 .gitignore aktualisiert 2026-03-18 10:59:12 +01:00
CPATRD 41f6e640e1 Packages aktualisiert 2026-03-18 10:53:51 +01:00
CPATRD 26f1902996 Moved userscript to client 2026-03-18 10:34:20 +01:00
CPAMAP c8b8199e93 No more /git - fixed wrong link 2026-03-18 08:38:08 +01:00
CPATRD 4a81cbf321 Bump version to 9.2.2 2024-10-15 17:13:21 +02:00
CPATRD b1d846de32 gm-fetch aktualisiert 2024-10-15 17:12:35 +02:00
CPATRD 748a8740eb Revert "gm-fetch für Firefox gefixt"
This reverts commit 20e011bb55.
2024-10-15 16:45:21 +02:00
CPATRD 248fbd5f0f Bump version to 9.2.0 2024-10-14 11:50:06 +02:00
CPATRD 20e011bb55 gm-fetch für Firefox gefixt 2024-10-14 11:49:10 +02:00
CPATRD bbb4a910a0 Bump version to 9.2.0 2024-10-14 11:01:38 +02:00
CPATRD 1cbde09ac6 Dep Update 2024-10-14 10:59:19 +02:00
CPATRD d107b1a49f 3cx 20 2024-10-14 09:55:09 +02:00
CPATRD 505bab6d34 Bump version to 9.1.1 2023-06-27 17:34:29 +02:00
CPATRD e1459856c2 Added line break to search results 2023-06-27 17:34:11 +02:00
CPATRD 72e7a95904 Bump version to 9.1.0 2023-06-27 17:25:14 +02:00
CPATRD 69e5857963 Fix tapi search results 2023-06-27 17:23:37 +02:00
CPATRD 9a0c476bc5 Packages aktualisiert 2023-06-27 16:42:05 +02:00
CPATRD e563279faf Bump version to 9.0.3 2022-10-03 09:52:08 +02:00
CPATRD 856181f530 Search window wurde nicht immer hinzugefügt 2022-10-03 09:50:41 +02:00
CPATRD ae0c125a50 Packages aktualisiert 2022-10-03 09:34:36 +02:00
69 changed files with 13638 additions and 20181 deletions
+24 -3
View File
@@ -1,4 +1,25 @@
node_modules
.idea
.vscode
dist
.vscode/*
client/node_modules
client/dist
.vs/
.user
.kiota.log
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
+3727 -7402
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
const pkg = require('../package.json')
module.exports = {
name: '3CX TAPI',
namespace: 'http://cp-solutions.at',
version: pkg.version,
author: pkg.author,
copyright: 'Copyright CP Solutions GmbH',
source: pkg.repository.url,
downloadURL: `${pkg.repository.url}/raw/branch/master/3CX_TAPI.user.js`,
match: [
'https://192.168.0.154:5001/*',
'https://cpsolution.my3cx.at:5001/*'
],
require: [
'https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.2/waitForKeyElements.js',
],
grant: [
'GM.xmlHttpRequest',
'GM.notification',
'GM.getValue',
'GM.setValue'
],
connect: [
'3cxtapi.cp-austria.at'
],
'run-at': 'document-end'
}
@@ -15,8 +15,6 @@ const webpackConfig = {
},
externals: {
jquery: '$',
axios: 'axios',
'axios-userscript-adapter': 'axiosGmxhrAdapter'
},
module: {
rules: [
@@ -44,6 +42,10 @@ const webpackConfig = {
'style-loader',
'css-loader',
]
},
{
test: /\.svg$/,
loader: 'svg-inline-loader'
}
]
},
@@ -1,7 +1,7 @@
const path = require('path')
const { merge } = require('webpack-merge')
const LiveReloadPlugin = require('webpack-livereload-plugin')
const UserScriptMetaDataPlugin = require('userscript-metadata-webpack-plugin')
const { UserScriptMetaDataPlugin } = require('userscript-metadata-webpack-plugin')
const metadata = require('./metadata.cjs')
const webpackConfig = require('./webpack.config.base.cjs')
@@ -1,5 +1,5 @@
const { merge } = require('webpack-merge')
const UserScriptMetaDataPlugin = require('userscript-metadata-webpack-plugin')
const { UserScriptMetaDataPlugin } = require('userscript-metadata-webpack-plugin')
const metadata = require('./metadata.cjs')
const webpackConfig = require('./webpack.config.base.cjs')
+7607
View File
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
{
"name": "3cx-tapi",
"description": "3CX CP Tapi and Projectmanager integration",
"version": "9.4.0",
"author": {
"name": "Daniel Triendl",
"email": "d.triendl@cp-solutions.at"
},
"eslintIgnore": [
"dist/*.js",
"node_modules"
],
"scripts": {
"lint": "eslint --ext .ts,.js src",
"preversion": "npm run lint",
"postversion": "git push --follow-tags",
"anylize": "npm_config_report=true npm run build",
"build": "webpack --mode production --config config/webpack.config.production.cjs",
"dev": "webpack --mode development --config config/webpack.config.dev.cjs"
},
"repository": {
"type": "git",
"url": "https://source.cp-austria.at/CPATRD/3cx_tapi.git"
},
"private": true,
"dependencies": {
"@trim21/gm-fetch": "^0.3.0",
"chrono-node": "^2.9.0"
},
"devDependencies": {
"@babel/core": "^7.29.0",
"@babel/preset-env": "^7.29.2",
"@types/greasemonkey": "^4.0.7",
"@typescript-eslint/eslint-plugin": "^8.57.1",
"@typescript-eslint/parser": "^8.57.1",
"babel-loader": "^10.1.1",
"browserslist": "^4.21.9",
"css-loader": "^7.1.4",
"eslint": "^9.39.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "^7.2.1",
"less": "^4.6.4",
"less-loader": "^12.3.2",
"style-loader": "^4.0.0",
"svg-inline-loader": "^0.8.2",
"ts-loader": "^9.5.4",
"typescript": "^5.9.3",
"userscript-metadata-webpack-plugin": "^0.4.2",
"webpack": "^5.105.4",
"webpack-bundle-analyzer": "^5.2.0",
"webpack-cli": "^7.0.2",
"webpack-livereload-plugin": "3.0.2",
"webpack-merge": "^6.0.1"
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ npm run build
## distribution
```
cp "dist/3CX TAPI.prod.user.js" 3CX_TAPI.user.js
cp "dist/3CX TAPI.prod.user.js" ../3CX_TAPI.user.js
```
And commit 3CX_TAPI.user.js
@@ -1,6 +1,9 @@
import * as chrono from 'chrono-node'
import { TapiContact } from './tapi-contact'
import { axios, extractNumber } from './utils'
import { extractNumber } from './utils'
import GM_fetch from '@trim21/gm-fetch'
import { Config } from './config'
const telephoneIcon = require('./telephone.svg');
export class CallHistory {
private callerIds: { [number: string]: TapiContact } = {}
@@ -22,9 +25,10 @@ export class CallHistory {
date = dateParts.groups.date
duration = dateParts.groups.duration
}
var parsedDate = chrono.de.parseDate(date)
if (!parsedDate) {
parsedDate = chrono.parseDate(date)
var parsedDate = chrono.parseDate(date)
var parsedDateDe = chrono.de.parseDate(date)
if (parsedDateDe) {
parsedDate = parsedDateDe
}
if (!parsedDate) {
return
@@ -55,18 +59,7 @@ export class CallHistory {
a.onclick = () => {
window.open(href)
}
a.innerHTML = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 559.98 559.98">' +
'<g>' +
' <g>' +
' <path d="M279.99,0C125.601,0,0,125.601,0,279.99c0,154.39,125.601,279.99,279.99,279.99c154.39,0,279.99-125.601,279.99-279.99' +
' C559.98,125.601,434.38,0,279.99,0z M279.99,498.78c-120.644,0-218.79-98.146-218.79-218.79' +
' c0-120.638,98.146-218.79,218.79-218.79s218.79,98.152,218.79,218.79C498.78,400.634,400.634,498.78,279.99,498.78z"/>' +
' <path d="M304.226,280.326V162.976c0-13.103-10.618-23.721-23.716-23.721c-13.102,0-23.721,10.618-23.721,23.721v124.928' +
' c0,0.373,0.092,0.723,0.11,1.096c-0.312,6.45,1.91,12.999,6.836,17.926l88.343,88.336c9.266,9.266,24.284,9.266,33.543,0' +
' c9.26-9.266,9.266-24.284,0-33.544L304.226,280.326z"/>' +
' </g>' +
'</g>' +
'</svg>'
a.innerHTML = telephoneIcon;
a.classList.add('btn');
a.classList.add('btn-plain');
@@ -84,10 +77,10 @@ export class CallHistory {
if (this.callerIds[number] !== undefined) {
this.updateCallHistoryEntry(element, this.callerIds[number])
} else {
var response = await axios.get<TapiContact>('http://cpatapi.cpsrvweb2016.cp-austria.at/callerid/' + encodeURIComponent(number))
var response = await GM_fetch(Config.tapi_server_url + '/callerid/' + encodeURIComponent(number))
var callerId: TapiContact = { tD_NAME: '' }
if (response.status === 200) {
callerId = response.data
callerId = await response.json() as TapiContact
}
console.log('TAPI call histroy callerid response', number, response, callerId)
this.callerIds[number] = callerId
@@ -1,5 +1,7 @@
import GM_fetch from '@trim21/gm-fetch'
import { TapiContact } from './tapi-contact'
import { axios, extractNumber } from './utils'
import { extractNumber } from './utils'
import { Config } from './config'
export class CallNotification {
public async showCallNotification (element: HTMLElement) {
@@ -13,19 +15,19 @@ export class CallNotification {
}
console.log('TAPI searching callerid for', number)
var response = await axios.get<TapiContact>('http://cpatapi.cpsrvweb2016.cp-austria.at/callerid/' + encodeURIComponent(number))
var response = await GM_fetch(Config.tapi_server_url + '/callerid/' + encodeURIComponent(number))
console.log('TAPI callerid response', response)
var notification = {
text: number
}
if (response.status === 200) {
var callerId = response.data
var callerId = await response.json() as TapiContact
if (callerId) {
notification.text = callerId.tD_NAME + '\r\n' + number + ' (' + callerId.tD_MEDIUM + ')'
}
}
// eslint-disable-next-line no-undef
GM.notification(notification)
GM.notification(notification.text, 'TAPI Anruf')
}
}
+5
View File
@@ -0,0 +1,5 @@
class _Config {
public tapi_server_url: string = 'https://3cxtapi.cp-austria.at'
}
export const Config = new _Config()
+4 -2
View File
@@ -9,7 +9,7 @@ console.log('script start')
const search = new Search()
// eslint-disable-next-line no-undef
waitForKeyElements('ongoing-call-button', (element) => { search.createSearchWindow(element) }, true)
waitForKeyElements('ongoing-call-button', (element) => { search.createSearchWindow(element) }, false)
const callNotification = new CallNotification()
// eslint-disable-next-line no-undef
@@ -17,8 +17,10 @@ waitForKeyElements('call-view', (element) => { callNotification.showCallNotifica
const callHistory = new CallHistory()
// eslint-disable-next-line no-undef
waitForKeyElements('.call-history-list call', (element) => { callHistory.showCallHistory(element) }, false)
waitForKeyElements('call', (element) => { callHistory.showCallHistory(element) }, false)
const status = new Status()
// eslint-disable-next-line no-undef
waitForKeyElements('wc-account-menu', (element) => { status.showStatus(element) }, false)
waitForKeyElements('wc-account-menu i.status-indicator', (element) => { status.watchStatus(element) }, false)
+42
View File
@@ -0,0 +1,42 @@
.tapi-search-autocomplete {
/*the container must be positioned relative:*/
position: relative;
display: inline-block;
margin-right: 20px;
}
.tapi-search-autocomplete input {
border: 1px solid transparent;
background-color: #f1f1f1;
/*padding: 10px;*/
/*font-size: 16px;*/
}
.tapi-search-autocomplete input[type=text] {
background-color: #f1f1f1;
width: 100%;
}
.tapi-search-autocomplete-items {
position: absolute;
border: 1px solid #d4d4d4;
border-bottom: none;
border-top: none;
z-index: 99;
/*position the autocomplete items to be the same width as the container:*/
top: 100%;
left: 0;
right: 0;
}
.tapi-search-autocomplete-items div {
padding: 10px;
cursor: pointer;
background-color: #fff;
border-bottom: 1px solid #d4d4d4;
color: #000;
}
.tapi-search-autocomplete-items div p {
margin: 0;
}
.tapi-search-autocomplete-items div:hover, .tapi-search-autocomplete-active {
/*when hovering an item:*/
background-color: #E7E6E6 !important;
}
+35 -65
View File
@@ -1,7 +1,9 @@
import './search.css'
import { TapiContact } from './tapi-contact'
import { debounce } from './debounce'
import { axios, fireChangeEvents } from './utils'
import { fireChangeEvents } from './utils'
import GM_fetch from '@trim21/gm-fetch'
import { Config } from './config'
export class Search {
private currentSearchText = ''
@@ -10,13 +12,10 @@ export class Search {
console.log('Create TAPI Search')
var form = document.createElement('form')
form.style.width = '200px'
form.style.float = 'right'
form.style.marginRight = '20px'
form.onsubmit = () => {
var items = document.getElementsByClassName('tapi-search-result-selected')
var items = document.getElementsByClassName('tapi-search-autocomplete-active')
if (items.length === 0) {
items = document.getElementsByClassName('tapi-search-result')
items = document.getElementsByClassName('tapi-search-autocomplete-item')
}
if (items.length > 0) {
this.dial((<HTMLElement>items[0]).dataset.tapiNumber)
@@ -28,23 +27,14 @@ export class Search {
}
var searchBox = document.createElement('div')
searchBox.classList.add('contact-search-box')
searchBox.classList.add('tapi-search-autocomplete')
searchBox.style.width = '200px'
searchBox.id = 'tapiSearchBox'
form.appendChild(searchBox)
var searchWrapper = document.createElement('div')
searchWrapper.classList.add('search-input-wrapper')
searchWrapper.style.position = 'relative'
searchBox.appendChild(searchWrapper)
var search = document.createElement('input')
search.id = 'tapiSearchInput'
search.autocomplete = 'off'
search.classList.add('padder')
search.classList.add('rounded')
search.classList.add('bg-light')
search.classList.add('no-border')
search.classList.add('contact-search-box')
search.placeholder = 'TAPI Suche'
search.onfocus = () => { this.doSearch() }
search.onkeydown = (ev) => { this.doSearchKeyDown(ev) }
@@ -53,22 +43,15 @@ export class Search {
setTimeout(() => {
console.log('TAPI clear search results')
this.removeSearchResults()
}, 500)
}, 250)
}
searchWrapper.appendChild(search)
var icon = document.createElement('span')
icon.classList.add('fa')
icon.classList.add('fa-search')
icon.classList.add('form-control-feedback')
icon.style.color = 'grey'
searchWrapper.appendChild(icon)
searchBox.appendChild(search)
element.parentElement.insertBefore(form, element)
}
private removeSearchResults () {
var resultList = document.getElementById('tapiResults')
var resultList = document.getElementById('tapi-search-autocomplete-list')
if (resultList) {
resultList.parentNode.removeChild(resultList)
}
@@ -77,12 +60,12 @@ export class Search {
private doSearchKeyDown (ev: KeyboardEvent) {
if (ev.key === 'ArrowUp') {
let items = document.getElementsByClassName('tapi-search-result-selected')
let items = document.getElementsByClassName('tapi-search-autocomplete-active')
if (items.length > 0) {
var prev = <Element>items[0].previousSibling
}
if (!prev) {
items = document.getElementsByClassName('tapi-search-result')
items = document.getElementsByClassName('tapi-search-autocomplete-item')
if (items.length > 0) {
prev = items[items.length - 1]
}
@@ -92,12 +75,12 @@ export class Search {
prev.scrollIntoView(true)
}
} else if (ev.key === 'ArrowDown') {
let items = document.getElementsByClassName('tapi-search-result-selected')
let items = document.getElementsByClassName('tapi-search-autocomplete-active')
if (items.length > 0) {
var next = <Element>items[0].nextSibling
}
if (!next) {
items = document.getElementsByClassName('tapi-search-result')
items = document.getElementsByClassName('tapi-search-autocomplete-item')
if (items.length > 0) {
next = items[0]
}
@@ -121,52 +104,39 @@ export class Search {
return
}
console.log('Searching TAPI')
var response = await axios.get<TapiContact[]>('http://cpatapi.cpsrvweb2016.cp-austria.at/search?query=' + encodeURIComponent(searchText))
var response = await GM_fetch(Config.tapi_server_url + '/search?query=' + encodeURIComponent(searchText))
console.log('TAPI Search response', response)
var contacts = response.data
var contacts = await response.json() as TapiContact[]
console.log('TAPI Contacts', contacts)
this.removeSearchResults()
this.currentSearchText = searchText
var resultList = document.createElement('ul')
resultList.id = 'tapiResults'
resultList.classList.add('search-nav-absolute')
resultList.classList.add('search-nav-ul')
document.getElementById('tapiSearchBox').appendChild(resultList)
var results = document.createElement('div');
results.setAttribute('id', 'tapi-search-autocomplete-list')
results.setAttribute('class', 'tapi-search-autocomplete-items')
document.getElementById('tapiSearchBox').appendChild(results)
resultList.innerHTML = ''
contacts.forEach(contact => {
var li = document.createElement('li')
li.classList.add('tapi-search-result')
li.classList.add('pointer')
li.onmouseover = () => { this.selectResult(li) }
li.dataset.tapiNumber = contact.tD_NUMBER_TAPI
li.onclick = () => { this.dial(contact.tD_NUMBER_TAPI) }
li.style.listStyle = 'outside none none' // display: flex; align-items: center;
var resultText = document.createElement('div')
resultText.classList.add('search-result-txt')
li.appendChild(resultText)
var line1 = document.createElement('div')
line1.appendChild(document.createTextNode(contact.tD_NAME))
resultText.appendChild(line1)
var line2 = document.createElement('div')
line2.appendChild(document.createTextNode(contact.tD_MEDIUM + ': ' + contact.tD_NUMBER_TAPI))
resultText.appendChild(line2)
resultList.appendChild(li)
var item = document.createElement('div');
item.setAttribute('class', 'tapi-search-autocomplete-item')
var p = document.createElement('p')
p.innerHTML = contact.tD_NAME + '<br>' + contact.tD_MEDIUM + ': ' + contact.tD_NUMBER_TAPI
item.appendChild(p)
item.onclick = () => { this.dial(contact.tD_NUMBER_TAPI) }
item.onmouseover = () => { this.selectResult(item) }
item.dataset.tapiNumber = contact.tD_NUMBER_TAPI
results.appendChild(item);
})
}, 200)
private selectResult (resultLi: Element) {
var items = document.getElementsByClassName('tapi-search-result')
for (var item of items) {
item.classList.remove('tapi-search-result-selected')
private selectResult (item: Element) {
console.log('Select item', item)
var items = document.getElementsByClassName('tapi-search-autocomplete-active')
for (var i of items) {
i.classList.remove('tapi-search-autocomplete-active')
}
resultLi.classList.add('tapi-search-result-selected')
item.classList.add('tapi-search-autocomplete-active')
}
private dial (number: string) {
+203
View File
@@ -0,0 +1,203 @@
import { Config } from './config';
import './status.css';
import { ZcStatus } from './zc-status';
import GM_fetch from "@trim21/gm-fetch";
const zcIcon = require('./stopwatch-regular-full.svg');
declare function waitForKeyElements(selectorOrFunction: any, callback: any, waitOnce: boolean): any;
export class Status {
private _user: string;
private _enabled = false;
private _statusOn = 'menuAvailable';
private _statusOff = 'menuAway';
private _currentStatus: boolean = undefined;
public async showStatus(element: HTMLElement) {
this._user = await GM.getValue('tapi-zc-user', '');
this._enabled = await GM.getValue('tapi-zc-enabled', false);
this._statusOn = await GM.getValue('tapi-zc-on', 'menuAvailable');
this._statusOff = await GM.getValue('tapi-zc-off', 'menuAvailable');
console.log('tapi-zc-user', this._user, 'tapi-zc-enabled', this._enabled, 'tapi-zc-on', this._statusOn, 'tapi-zc-off', this._statusOff);
this.checkStatus();
waitForKeyElements("wc-account-menu > div > ul", (element: HTMLElement) => { this.addZcStatusPopup(element) }, true);
}
private async checkStatus() {
if (this._enabled) {
try {
var response = await GM_fetch(Config.tapi_server_url + '/availability/' + encodeURIComponent(this._user));
if (response.status == 200) {
var status = await response.json() as ZcStatus;
if (this._currentStatus !== status.loggedIn) {
this._currentStatus = status.loggedIn;
console.log('New status, loggedIn', this._currentStatus);
var accMenu = document.getElementsByTagName("wc-account-menu")[0];
var avatar = accMenu.getElementsByTagName("app-avatar")[0] as HTMLAnchorElement;
avatar.click();
setTimeout(() => {
var statusId = this._currentStatus ? this._statusOn : this._statusOff;
console.log('Clicking status', statusId);
(document.getElementById(statusId) as HTMLSpanElement).click();
}, 1000);
}
}
} catch (error) {
console.log(error);
}
setTimeout(() => this.checkStatus(), 30000);
}
}
private addZcStatusPopup(element: HTMLElement) {
var divider = document.createElement('li');
divider.classList.add('divider');
divider.classList.add('dropdown-divider');
element.appendChild(divider);
var menu = document.createElement('li');
menu.role = 'menuitem';
element.appendChild(menu);
var link = document.createElement('a');
link.id = 'tapi-zc-button';
//link.innerText = 'ZeitConsens';
link.classList.add('dropdown-item');
link.classList.add('d-flex');
link.classList.add('align-items-center');
link.classList.add('gap-2');
link.onclick = () => {
document.getElementById('zc-modal').classList.toggle('show');
}
menu.appendChild(link);
var icon = document.createElement('span');
icon.classList.add('icon');
icon.classList.add('svg-xs');
icon.innerHTML = zcIcon;
link.appendChild(icon);
var text = document.createElement('span');
text.innerText = 'ZeitConsens';
link.appendChild(text);
var html =
'<div role="document" class="modal-dialog">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <h4 class="modal-title">ZeitConsens Status</h4><button id="zc-btnClose" type="button" aria-label="Close" class="btn-close" data-qa="modal-cross"></button>' +
' </div>' +
' <div class="modal-body">' +
' <div class="form-group">' +
' <label for="tapi-zc-user">Username</label>' +
' <input type="text" class="form-control" name="tapi-zc-user" id="tapi-zc-user">' +
' </div>' +
' <div class="form-group">' +
' <label for="tapi-zc-on">Signed in</label>' +
' <select id="tapi-zc-on" class="form-control">' +
' <option value="menuAvailable">Available</option>' +
' <option value="menuOutofoffice">Do Not Disturb</option>' +
' <option value="menuCustom1">Verfügbar DW</option>' +
' </select>' +
' </div>' +
' <div class="form-group">' +
' <label for="tapi-zc-off">Signed out</label>' +
' <select id="tapi-zc-off" class="form-control">' +
' <option value="menuAway">Away</option>' +
' <option value="menuOutofoffice">Do Not Disturb</option>' +
' </select>' +
' </div>' +
' <div class="checkbox">' +
' <label class="i-checks" for="tapi-zc-enabled">' +
' <input type="checkbox" id="tapi-zc-enabled">' +
' <i></i><span>Enabled</span>' +
' </label>' +
' </div>' +
' </div>' +
//' <div class="modal-footer">' +
//' <button id="zc-btnOk" type="button" class="btn btn-primary" data-qa="modal-ok">OK </button>' +
//' <button id="zc-btnCancel" type="button" class="btn btn-border" data-qa="modal-close">Cancel </button>' +
//' </div>' +
' </div>' +
'</div>';
var modal = document.createElement('modal-container');
modal.id = 'zc-modal';
modal.classList.add('modal');
modal.classList.add('fade');
modal.innerHTML = html;
document.getElementsByTagName('body')[0].appendChild(modal);
var btnClose = document.getElementById('zc-btnClose');
btnClose.onclick = () => {
document.getElementById('zc-modal').classList.toggle('show');
}
var zcUser = document.getElementById('tapi-zc-user') as HTMLInputElement;
zcUser.value = this._user;
zcUser.onchange = () => {
this._user = zcUser.value;
GM.setValue('tapi-zc-user', this._user);
console.log('tapi-zc-user', this._user);
this._currentStatus = undefined;
}
var zcEnabled = document.getElementById('tapi-zc-enabled') as HTMLInputElement;
zcEnabled.checked = this._enabled;
zcEnabled.onchange = () => {
this._enabled = zcEnabled.checked;
GM.setValue('tapi-zc-enabled', this._enabled);
console.log('tapi-zc-enabled', this._enabled);
this._currentStatus = undefined;
this.checkStatus();
}
var zcOn = document.getElementById('tapi-zc-on') as HTMLSelectElement;
zcOn.value = this._statusOn;
zcOn.onchange = () => {
this._statusOn = zcOn.value;
GM.setValue('tapi-zc-on', this._statusOn);
console.log('tapi-zc-on', this._statusOn);
this._currentStatus = undefined;
}
var zcOff = document.getElementById('tapi-zc-off') as HTMLSelectElement;
zcOff.value = this._statusOff;
zcOff.onchange = () => {
this._statusOff = zcOff.value;
GM.setValue('tapi-zc-off', this._statusOff);
console.log('tapi-zc-off', this._statusOff);
this._currentStatus = undefined;
}
}
public watchStatus(element: HTMLElement) {
console.log('updateFavicon watching', element);
const attrObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
this.updateStatus(element);
}
});
});
attrObserver.observe(element, { attributes: true });
this.updateStatus(element);
}
private updateStatus(element: HTMLElement) {
console.log('Status changed', element.classList);
var color = window.getComputedStyle(element).getPropertyValue("background-color");
console.log('Background color:', color);
this.setFaviconColor(color);
}
private setFaviconColor(color: string) {
var link = document.querySelector("link[rel~='icon']") as HTMLLinkElement;
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
document.head.appendChild(link);
}
link.href = "data:image/svg+xml,%3Csvg width='32px' height='32px' xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cpolyline points='9,0 20,0 32,16 20,32 9,32, 20,16' fill='" + encodeURIComponent(color) + "' id='MyLine' /%3E%3C/svg%3E";
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Pro 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2026 Fonticons, Inc.--><path d="M240 88C240 74.7 250.7 64 264 64L376 64C389.3 64 400 74.7 400 88C400 101.3 389.3 112 376 112L344 112L344 137.3C393.6 142.8 438.1 165 471.8 198.3L495 175C504.4 165.6 519.6 165.6 528.9 175C538.2 184.4 538.3 199.6 528.9 208.9L502.1 235.7C523.5 269.2 536 309.1 536 351.9C536 471.2 439.3 567.9 320 567.9C200.7 567.9 104 471.3 104 352C104 240.8 188 149.3 296 137.3L296 112L264 112C250.7 112 240 101.3 240 88zM320 184C227.2 184 152 259.2 152 352C152 444.8 227.2 520 320 520C412.8 520 488 444.8 488 352C488 259.2 412.8 184 320 184zM344 248L344 352C344 365.3 333.3 376 320 376C306.7 376 296 365.3 296 352L296 248C296 234.7 306.7 224 320 224C333.3 224 344 234.7 344 248z"/></svg>

After

Width:  |  Height:  |  Size: 906 B

+12
View File
@@ -0,0 +1,12 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 559.98 559.98">
<g>
<g>
<path d="M279.99,0C125.601,0,0,125.601,0,279.99c0,154.39,125.601,279.99,279.99,279.99c154.39,0,279.99-125.601,279.99-279.99
C559.98,125.601,434.38,0,279.99,0z M279.99,498.78c-120.644,0-218.79-98.146-218.79-218.79
c0-120.638,98.146-218.79,218.79-218.79s218.79,98.152,218.79,218.79C498.78,400.634,400.634,498.78,279.99,498.78z"/>
<path d="M304.226,280.326V162.976c0-13.103-10.618-23.721-23.716-23.721c-13.102,0-23.721,10.618-23.721,23.721v124.928
c0,0.373,0.092,0.723,0.11,1.096c-0.312,6.45,1.91,12.999,6.836,17.926l88.343,88.336c9.266,9.266,24.284,9.266,33.543,0
c9.26-9.266,9.266-24.284,0-33.544L304.226,280.326z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 821 B

-20
View File
@@ -1,23 +1,3 @@
/**
* @typedef {Object} AxiosResponse
* @property {Object} data
* @property {Object} headers
* @property {Object} config
* @property {Object} request
* @property {number} code
* @property {string} statusText
*/
/**
* @typedef {Object} AxiosError
* @property {AxiosResponse} response
*/
import axios from 'axios'
import adapter from 'axios-userscript-adapter'
axios.defaults.adapter = adapter
export { axios }
export function extractNumber (s: string) {
var match = /(\+?[0-9]{4,})/.exec(s)
if (!match) {
+2 -2
View File
@@ -2,8 +2,8 @@
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es6",
"module": "ESNext",
"target": "ES2022",
"allowJs": true,
"moduleResolution": "node"
}
-30
View File
@@ -1,30 +0,0 @@
const pkg = require('../package.json')
module.exports = {
name: '3CX TAPI',
namespace: 'http://cp-solutions.at',
version: pkg.version,
author: pkg.author,
copyright: 'Copyright 2021 CP Solutions GmbH',
source: pkg.repository.url,
downloadURL: 'https://source.cp-austria.at/git/CPATRD/3cx_tapi/raw/branch/master/3CX_TAPI.user.js',
match: [
'https://192.168.0.154:5001/webclient*',
'https://cpsolution.my3cx.at:5001/webclient*'
],
require: [
'https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.2/waitForKeyElements.js',
`https://cdn.jsdelivr.net/npm/axios@${pkg.dependencies.axios}/dist/axios.min.js`,
`https://cdn.jsdelivr.net/npm/axios-userscript-adapter@${pkg.dependencies['axios-userscript-adapter']}/dist/axiosGmxhrAdapter.min.js`
],
grant: [
'GM.xmlHttpRequest',
'GM.notification',
'GM.getValue',
'GM.setValue'
],
connect: [
'cpatapi.cpsrvweb2016.cp-austria.at'
],
'run-at': 'document-end'
}
-12144
View File
File diff suppressed because it is too large Load Diff
-57
View File
@@ -1,57 +0,0 @@
{
"name": "3cx-tapi",
"description": "3CX CP Tapi and Projectmanager integration",
"version": "9.0.2",
"author": {
"name": "Daniel Triendl",
"email": "d.triendl@cp-solutions.at"
},
"eslintIgnore": [
"dist/*.js",
"node_modules"
],
"scripts": {
"lint": "eslint --ext .ts,.js src",
"preversion": "npm run lint",
"postversion": "git push --follow-tags",
"anylize": "npm_config_report=true npm run build",
"build": "webpack --mode production --config config/webpack.config.production.cjs",
"dev": "webpack --mode development --config config/webpack.config.dev.cjs"
},
"repository": {
"type": "git",
"url": "https://source.cp-austria.at/git/CPATRD/3cx_tapi.git"
},
"private": true,
"dependencies": {
"axios": "0.21.1",
"axios-userscript-adapter": "0.1.4",
"chrono-node": "^2.3.0"
},
"devDependencies": {
"@babel/core": "7.14.6",
"@babel/preset-env": "7.14.5",
"@typescript-eslint/eslint-plugin": "4.27.0",
"@typescript-eslint/parser": "4.27.0",
"babel-loader": "8.2.2",
"browserslist": "4.16.6",
"css-loader": "5.2.6",
"eslint": "7.29.0",
"eslint-config-standard": "16.0.3",
"eslint-plugin-import": "2.23.4",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "5.1.0",
"eslint-plugin-standard": "4.1.0",
"less": "4.1.1",
"less-loader": "10.0.0",
"style-loader": "2.0.0",
"ts-loader": "9.2.3",
"typescript": "4.3.4",
"userscript-metadata-webpack-plugin": "0.1.0",
"webpack": "5.39.1",
"webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "4.7.2",
"webpack-livereload-plugin": "3.0.1",
"webpack-merge": "5.8.0"
}
}
+30
View File
@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
+40
View File
@@ -0,0 +1,40 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.5.11612.153
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CPATapi.Server", "src\CPATapi.Server\CPATapi.Server.csproj", "{7879A024-A074-FE67-0546-8668213BFA99}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CPATapi.Client", "src\CPATapi.Client\CPATapi.Client.csproj", "{C60C2505-6D82-4058-9392-FA3C8F57FF8A}"
ProjectSection(ProjectDependencies) = postProject
{7879A024-A074-FE67-0546-8668213BFA99} = {7879A024-A074-FE67-0546-8668213BFA99}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CPATapi.Client.Tests", "test\CPATapi.Client.Tests\CPATapi.Client.Tests.csproj", "{17F37791-4F68-46D5-8CF5-5F1736F6776E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7879A024-A074-FE67-0546-8668213BFA99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7879A024-A074-FE67-0546-8668213BFA99}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7879A024-A074-FE67-0546-8668213BFA99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7879A024-A074-FE67-0546-8668213BFA99}.Release|Any CPU.Build.0 = Release|Any CPU
{C60C2505-6D82-4058-9392-FA3C8F57FF8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C60C2505-6D82-4058-9392-FA3C8F57FF8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C60C2505-6D82-4058-9392-FA3C8F57FF8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C60C2505-6D82-4058-9392-FA3C8F57FF8A}.Release|Any CPU.Build.0 = Release|Any CPU
{17F37791-4F68-46D5-8CF5-5F1736F6776E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17F37791-4F68-46D5-8CF5-5F1736F6776E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17F37791-4F68-46D5-8CF5-5F1736F6776E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17F37791-4F68-46D5-8CF5-5F1736F6776E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E2CF5765-9038-4ED6-AC5C-1A3E569ABC6C}
EndGlobalSection
EndGlobal
@@ -0,0 +1,54 @@
// <auto-generated/>
#pragma warning disable CS0618
using CPATapi.Client.Availability.Item;
using CPATapi.Client.Availability.Users;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System;
namespace CPATapi.Client.Availability
{
/// <summary>
/// Builds and executes requests for operations under \Availability
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class AvailabilityRequestBuilder : BaseRequestBuilder
{
/// <summary>The users property</summary>
public global::CPATapi.Client.Availability.Users.UsersRequestBuilder Users
{
get => new global::CPATapi.Client.Availability.Users.UsersRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>Gets an item from the CPATapi.Client.Availability.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::CPATapi.Client.Availability.Item.WithUserItemRequestBuilder"/></returns>
public global::CPATapi.Client.Availability.Item.WithUserItemRequestBuilder this[string position]
{
get
{
var urlTplParams = new Dictionary<string, object>(PathParameters);
urlTplParams.Add("user", position);
return new global::CPATapi.Client.Availability.Item.WithUserItemRequestBuilder(urlTplParams, RequestAdapter);
}
}
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Availability.AvailabilityRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public AvailabilityRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/Availability", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Availability.AvailabilityRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public AvailabilityRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/Availability", rawUrl)
{
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,86 @@
// <auto-generated/>
#pragma warning disable CS0618
using CPATapi.Client.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace CPATapi.Client.Availability.Item
{
/// <summary>
/// Builds and executes requests for operations under \Availability\{user}
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithUserItemRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Availability.Item.WithUserItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithUserItemRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/Availability/{user}", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Availability.Item.WithUserItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithUserItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/Availability/{user}", rawUrl)
{
}
/// <returns>A <see cref="global::CPATapi.Client.Models.Availability"/></returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<global::CPATapi.Client.Models.Availability?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<global::CPATapi.Client.Models.Availability> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
return await RequestAdapter.SendAsync<global::CPATapi.Client.Models.Availability>(requestInfo, global::CPATapi.Client.Models.Availability.CreateFromDiscriminatorValue, default, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::CPATapi.Client.Availability.Item.WithUserItemRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::CPATapi.Client.Availability.Item.WithUserItemRequestBuilder WithUrl(string rawUrl)
{
return new global::CPATapi.Client.Availability.Item.WithUserItemRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithUserItemRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,86 @@
// <auto-generated/>
#pragma warning disable CS0618
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace CPATapi.Client.Availability.Users
{
/// <summary>
/// Builds and executes requests for operations under \Availability\users
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class UsersRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Availability.Users.UsersRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public UsersRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/Availability/users", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Availability.Users.UsersRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public UsersRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/Availability/users", rawUrl)
{
}
/// <returns>A List&lt;string&gt;</returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<List<string>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<List<string>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var collectionResult = await RequestAdapter.SendPrimitiveCollectionAsync<string>(requestInfo, default, cancellationToken).ConfigureAwait(false);
return collectionResult?.AsList();
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::CPATapi.Client.Availability.Users.UsersRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::CPATapi.Client.Availability.Users.UsersRequestBuilder WithUrl(string rawUrl)
{
return new global::CPATapi.Client.Availability.Users.UsersRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class UsersRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>CPATapi.Client</PackageId>
<Authors>Daniel Triendl</Authors>
<Company>CP Solutions GmbH</Company>
<Version>9.4.0</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.5" />
<PackageReference Include="Microsoft.Kiota.Bundle" Version="1.22.1" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="kiota generate -l CSharp -c CPATapiClient -n CPATapi.Client -d ..\CPATapi.Server\CPATapi.Server.json -o .\" />
</Target>
</Project>
@@ -0,0 +1,61 @@
// <auto-generated/>
#pragma warning disable CS0618
using CPATapi.Client.Availability;
using CPATapi.Client.CallerId;
using CPATapi.Client.Contact;
using CPATapi.Client.Search;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Serialization.Form;
using Microsoft.Kiota.Serialization.Json;
using Microsoft.Kiota.Serialization.Multipart;
using Microsoft.Kiota.Serialization.Text;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System;
namespace CPATapi.Client
{
/// <summary>
/// The main entry point of the SDK, exposes the configuration and the fluent API.
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class CPATapiClient : BaseRequestBuilder
{
/// <summary>The Availability property</summary>
public global::CPATapi.Client.Availability.AvailabilityRequestBuilder Availability
{
get => new global::CPATapi.Client.Availability.AvailabilityRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The CallerId property</summary>
public global::CPATapi.Client.CallerId.CallerIdRequestBuilder CallerId
{
get => new global::CPATapi.Client.CallerId.CallerIdRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The Contact property</summary>
public global::CPATapi.Client.Contact.ContactRequestBuilder Contact
{
get => new global::CPATapi.Client.Contact.ContactRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The Search property</summary>
public global::CPATapi.Client.Search.SearchRequestBuilder Search
{
get => new global::CPATapi.Client.Search.SearchRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.CPATapiClient"/> and sets the default values.
/// </summary>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public CPATapiClient(IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}", new Dictionary<string, object>())
{
ApiClientBuilder.RegisterDefaultSerializer<JsonSerializationWriterFactory>();
ApiClientBuilder.RegisterDefaultSerializer<TextSerializationWriterFactory>();
ApiClientBuilder.RegisterDefaultSerializer<FormSerializationWriterFactory>();
ApiClientBuilder.RegisterDefaultSerializer<MultipartSerializationWriterFactory>();
ApiClientBuilder.RegisterDefaultDeserializer<JsonParseNodeFactory>();
ApiClientBuilder.RegisterDefaultDeserializer<TextParseNodeFactory>();
ApiClientBuilder.RegisterDefaultDeserializer<FormParseNodeFactory>();
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,48 @@
// <auto-generated/>
#pragma warning disable CS0618
using CPATapi.Client.CallerId.Item;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System;
namespace CPATapi.Client.CallerId
{
/// <summary>
/// Builds and executes requests for operations under \CallerId
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class CallerIdRequestBuilder : BaseRequestBuilder
{
/// <summary>Gets an item from the CPATapi.Client.CallerId.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::CPATapi.Client.CallerId.Item.WithNumberItemRequestBuilder"/></returns>
public global::CPATapi.Client.CallerId.Item.WithNumberItemRequestBuilder this[string position]
{
get
{
var urlTplParams = new Dictionary<string, object>(PathParameters);
urlTplParams.Add("number", position);
return new global::CPATapi.Client.CallerId.Item.WithNumberItemRequestBuilder(urlTplParams, RequestAdapter);
}
}
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.CallerId.CallerIdRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public CallerIdRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/CallerId", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.CallerId.CallerIdRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public CallerIdRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/CallerId", rawUrl)
{
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,91 @@
// <auto-generated/>
#pragma warning disable CS0618
using CPATapi.Client.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace CPATapi.Client.CallerId.Item
{
/// <summary>
/// Builds and executes requests for operations under \CallerId\{number}
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithNumberItemRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.CallerId.Item.WithNumberItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithNumberItemRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/CallerId/{number}", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.CallerId.Item.WithNumberItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithNumberItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/CallerId/{number}", rawUrl)
{
}
/// <returns>A <see cref="global::CPATapi.Client.Models.TapiContact"/></returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::CPATapi.Client.Models.ProblemDetails">When receiving a 404 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<global::CPATapi.Client.Models.TapiContact?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<global::CPATapi.Client.Models.TapiContact> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "404", global::CPATapi.Client.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
return await RequestAdapter.SendAsync<global::CPATapi.Client.Models.TapiContact>(requestInfo, global::CPATapi.Client.Models.TapiContact.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::CPATapi.Client.CallerId.Item.WithNumberItemRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::CPATapi.Client.CallerId.Item.WithNumberItemRequestBuilder WithUrl(string rawUrl)
{
return new global::CPATapi.Client.CallerId.Item.WithNumberItemRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithNumberItemRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,87 @@
// <auto-generated/>
#pragma warning disable CS0618
using CPATapi.Client.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace CPATapi.Client.Contact
{
/// <summary>
/// Builds and executes requests for operations under \Contact
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class ContactRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Contact.ContactRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public ContactRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/Contact", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Contact.ContactRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public ContactRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/Contact", rawUrl)
{
}
/// <returns>A List&lt;global::CPATapi.Client.Models.TapiContact&gt;</returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<List<global::CPATapi.Client.Models.TapiContact>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<List<global::CPATapi.Client.Models.TapiContact>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var collectionResult = await RequestAdapter.SendCollectionAsync<global::CPATapi.Client.Models.TapiContact>(requestInfo, global::CPATapi.Client.Models.TapiContact.CreateFromDiscriminatorValue, default, cancellationToken).ConfigureAwait(false);
return collectionResult?.AsList();
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "application/json");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::CPATapi.Client.Contact.ContactRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::CPATapi.Client.Contact.ContactRequestBuilder WithUrl(string rawUrl)
{
return new global::CPATapi.Client.Contact.ContactRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class ContactRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,69 @@
// <auto-generated/>
#pragma warning disable CS0618
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using System.Collections.Generic;
using System.IO;
using System;
namespace CPATapi.Client.Models
{
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
#pragma warning disable CS1591
public partial class Availability : IAdditionalDataHolder, IParsable
#pragma warning restore CS1591
{
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; }
/// <summary>The loggedIn property</summary>
public bool? LoggedIn { get; set; }
/// <summary>The user property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? User { get; set; }
#nullable restore
#else
public string User { get; set; }
#endif
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Models.Availability"/> and sets the default values.
/// </summary>
public Availability()
{
AdditionalData = new Dictionary<string, object>();
}
/// <summary>
/// Creates a new instance of the appropriate class based on discriminator value
/// </summary>
/// <returns>A <see cref="global::CPATapi.Client.Models.Availability"/></returns>
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
public static global::CPATapi.Client.Models.Availability CreateFromDiscriminatorValue(IParseNode parseNode)
{
if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode));
return new global::CPATapi.Client.Models.Availability();
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary&lt;string, Action&lt;IParseNode&gt;&gt;</returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "loggedIn", n => { LoggedIn = n.GetBoolValue(); } },
{ "user", n => { User = n.GetStringValue(); } },
};
}
/// <summary>
/// Serializes information the current object
/// </summary>
/// <param name="writer">Serialization writer to use to serialize this model</param>
public virtual void Serialize(ISerializationWriter writer)
{
if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer));
writer.WriteBoolValue("loggedIn", LoggedIn);
writer.WriteStringValue("user", User);
writer.WriteAdditionalData(AdditionalData);
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,108 @@
// <auto-generated/>
#pragma warning disable CS0618
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System;
namespace CPATapi.Client.Models
{
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
#pragma warning disable CS1591
public partial class ProblemDetails : ApiException, IAdditionalDataHolder, IParsable
#pragma warning restore CS1591
{
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; }
/// <summary>The detail property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? Detail { get; set; }
#nullable restore
#else
public string Detail { get; set; }
#endif
/// <summary>The instance property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? Instance { get; set; }
#nullable restore
#else
public string Instance { get; set; }
#endif
/// <summary>The primary error message.</summary>
public override string Message { get => base.Message; }
/// <summary>The status property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public UntypedNode? Status { get; set; }
#nullable restore
#else
public UntypedNode Status { get; set; }
#endif
/// <summary>The title property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? Title { get; set; }
#nullable restore
#else
public string Title { get; set; }
#endif
/// <summary>The type property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? Type { get; set; }
#nullable restore
#else
public string Type { get; set; }
#endif
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Models.ProblemDetails"/> and sets the default values.
/// </summary>
public ProblemDetails()
{
AdditionalData = new Dictionary<string, object>();
}
/// <summary>
/// Creates a new instance of the appropriate class based on discriminator value
/// </summary>
/// <returns>A <see cref="global::CPATapi.Client.Models.ProblemDetails"/></returns>
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
public static global::CPATapi.Client.Models.ProblemDetails CreateFromDiscriminatorValue(IParseNode parseNode)
{
if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode));
return new global::CPATapi.Client.Models.ProblemDetails();
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary&lt;string, Action&lt;IParseNode&gt;&gt;</returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "detail", n => { Detail = n.GetStringValue(); } },
{ "instance", n => { Instance = n.GetStringValue(); } },
{ "status", n => { Status = n.GetObjectValue<UntypedNode>(UntypedNode.CreateFromDiscriminatorValue); } },
{ "title", n => { Title = n.GetStringValue(); } },
{ "type", n => { Type = n.GetStringValue(); } },
};
}
/// <summary>
/// Serializes information the current object
/// </summary>
/// <param name="writer">Serialization writer to use to serialize this model</param>
public virtual void Serialize(ISerializationWriter writer)
{
if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer));
writer.WriteStringValue("detail", Detail);
writer.WriteStringValue("instance", Instance);
writer.WriteObjectValue<UntypedNode>("status", Status);
writer.WriteStringValue("title", Title);
writer.WriteStringValue("type", Type);
writer.WriteAdditionalData(AdditionalData);
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,105 @@
// <auto-generated/>
#pragma warning disable CS0618
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using System.Collections.Generic;
using System.IO;
using System;
namespace CPATapi.Client.Models
{
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
#pragma warning disable CS1591
public partial class TapiContact : IAdditionalDataHolder, IParsable
#pragma warning restore CS1591
{
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; }
/// <summary>The tD_ID property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? TDID { get; set; }
#nullable restore
#else
public string TDID { get; set; }
#endif
/// <summary>The tD_MEDIUM property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? TDMEDIUM { get; set; }
#nullable restore
#else
public string TDMEDIUM { get; set; }
#endif
/// <summary>The tD_NAME property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? TDNAME { get; set; }
#nullable restore
#else
public string TDNAME { get; set; }
#endif
/// <summary>The tD_NUMBER property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? TDNUMBER { get; set; }
#nullable restore
#else
public string TDNUMBER { get; set; }
#endif
/// <summary>The tD_NUMBER_TAPI property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? TDNUMBERTAPI { get; set; }
#nullable restore
#else
public string TDNUMBERTAPI { get; set; }
#endif
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Models.TapiContact"/> and sets the default values.
/// </summary>
public TapiContact()
{
AdditionalData = new Dictionary<string, object>();
}
/// <summary>
/// Creates a new instance of the appropriate class based on discriminator value
/// </summary>
/// <returns>A <see cref="global::CPATapi.Client.Models.TapiContact"/></returns>
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
public static global::CPATapi.Client.Models.TapiContact CreateFromDiscriminatorValue(IParseNode parseNode)
{
if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode));
return new global::CPATapi.Client.Models.TapiContact();
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary&lt;string, Action&lt;IParseNode&gt;&gt;</returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "tD_ID", n => { TDID = n.GetStringValue(); } },
{ "tD_MEDIUM", n => { TDMEDIUM = n.GetStringValue(); } },
{ "tD_NAME", n => { TDNAME = n.GetStringValue(); } },
{ "tD_NUMBER", n => { TDNUMBER = n.GetStringValue(); } },
{ "tD_NUMBER_TAPI", n => { TDNUMBERTAPI = n.GetStringValue(); } },
};
}
/// <summary>
/// Serializes information the current object
/// </summary>
/// <param name="writer">Serialization writer to use to serialize this model</param>
public virtual void Serialize(ISerializationWriter writer)
{
if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer));
writer.WriteStringValue("tD_ID", TDID);
writer.WriteStringValue("tD_MEDIUM", TDMEDIUM);
writer.WriteStringValue("tD_NAME", TDNAME);
writer.WriteStringValue("tD_NUMBER", TDNUMBER);
writer.WriteStringValue("tD_NUMBER_TAPI", TDNUMBERTAPI);
writer.WriteAdditionalData(AdditionalData);
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,102 @@
// <auto-generated/>
#pragma warning disable CS0618
using CPATapi.Client.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace CPATapi.Client.Search
{
/// <summary>
/// Builds and executes requests for operations under \Search
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class SearchRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Search.SearchRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public SearchRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/Search{?query*}", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::CPATapi.Client.Search.SearchRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public SearchRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/Search{?query*}", rawUrl)
{
}
/// <returns>A List&lt;global::CPATapi.Client.Models.TapiContact&gt;</returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<List<global::CPATapi.Client.Models.TapiContact>?> GetAsync(Action<RequestConfiguration<global::CPATapi.Client.Search.SearchRequestBuilder.SearchRequestBuilderGetQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<List<global::CPATapi.Client.Models.TapiContact>> GetAsync(Action<RequestConfiguration<global::CPATapi.Client.Search.SearchRequestBuilder.SearchRequestBuilderGetQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var collectionResult = await RequestAdapter.SendCollectionAsync<global::CPATapi.Client.Models.TapiContact>(requestInfo, global::CPATapi.Client.Models.TapiContact.CreateFromDiscriminatorValue, default, cancellationToken).ConfigureAwait(false);
return collectionResult?.AsList();
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<global::CPATapi.Client.Search.SearchRequestBuilder.SearchRequestBuilderGetQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<global::CPATapi.Client.Search.SearchRequestBuilder.SearchRequestBuilderGetQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::CPATapi.Client.Search.SearchRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::CPATapi.Client.Search.SearchRequestBuilder WithUrl(string rawUrl)
{
return new global::CPATapi.Client.Search.SearchRequestBuilder(rawUrl, RequestAdapter);
}
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
#pragma warning disable CS1591
public partial class SearchRequestBuilderGetQueryParameters
#pragma warning restore CS1591
{
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
[QueryParameter("query")]
public string? Query { get; set; }
#nullable restore
#else
[QueryParameter("query")]
public string Query { get; set; }
#endif
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class SearchRequestBuilderGetRequestConfiguration : RequestConfiguration<global::CPATapi.Client.Search.SearchRequestBuilder.SearchRequestBuilderGetQueryParameters>
{
}
}
}
#pragma warning restore CS0618
@@ -0,0 +1,53 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Http.HttpClientLibrary;
namespace CPATapi.Client;
public static class ServiceExtensions
{
public static IServiceCollection AddCPATapiClient(this IServiceCollection services, Action<IServiceProvider, HttpClient> configureOptions)
{
services.AddKiotaHandlers();
services.AddHttpClient<CPATapiClientFactory>((sp, client) =>
{
configureOptions.Invoke(sp, client);
}).AttachKiotaHandlers();
services.AddTransient(sp => sp.GetRequiredService<CPATapiClientFactory>().GetClient());
return services;
}
private static IServiceCollection AddKiotaHandlers(this IServiceCollection services)
{
var handlers = KiotaClientFactory.GetDefaultHandlerActivatableTypes();
foreach (var handler in handlers)
{
if (services.All(s => s.ServiceType != handler))
{
services.AddScoped(handler);
}
}
return services;
}
private static IHttpClientBuilder AttachKiotaHandlers(this IHttpClientBuilder builder)
{
var handlers = KiotaClientFactory.GetDefaultHandlerActivatableTypes();
foreach (var handler in handlers)
{
builder.AddHttpMessageHandler((sp) => (DelegatingHandler)sp.GetRequiredService(handler));
}
return builder;
}
}
internal class CPATapiClientFactory(HttpClient httpClient)
{
private readonly HttpClient _httpClient = httpClient;
private readonly AnonymousAuthenticationProvider _authenticationProvider = new();
public CPATapiClient GetClient()
{
return new CPATapiClient(new HttpClientRequestAdapter(_authenticationProvider, httpClient: _httpClient));
}
}
+34
View File
@@ -0,0 +1,34 @@
{
"descriptionHash": "1B47F98D82C5E24FB3ABEDB3BF90615424A8F19620EA2621D23B7FC69F4F7BF27D512A11098EF4D8940C7D243DEEB59C0CA038264430AD9150B612F5046C8146",
"descriptionLocation": "../CPATapi.Server/CPATapi.Server.json",
"lockFileVersion": "1.0.0",
"kiotaVersion": "1.30.0",
"clientClassName": "CPATapiClient",
"typeAccessModifier": "Public",
"clientNamespaceName": "CPATapi.Client",
"language": "CSharp",
"usesBackingStore": false,
"excludeBackwardCompatible": false,
"includeAdditionalData": true,
"disableSSLValidation": false,
"serializers": [
"Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory",
"Microsoft.Kiota.Serialization.Text.TextSerializationWriterFactory",
"Microsoft.Kiota.Serialization.Form.FormSerializationWriterFactory",
"Microsoft.Kiota.Serialization.Multipart.MultipartSerializationWriterFactory"
],
"deserializers": [
"Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory",
"Microsoft.Kiota.Serialization.Text.TextParseNodeFactory",
"Microsoft.Kiota.Serialization.Form.FormParseNodeFactory"
],
"structuredMimeTypes": [
"application/json",
"text/plain;q=0.9",
"application/x-www-form-urlencoded;q=0.2",
"multipart/form-data;q=0.1"
],
"includePatterns": [],
"excludePatterns": [],
"disabledValidationRules": []
}
+1
View File
@@ -0,0 +1 @@
CPATapi.Server.json
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>a7b40068-a2f6-4c63-bbd9-0fd346908fb0</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
<OpenApiDocumentsDirectory>.</OpenApiDocumentsDirectory>
<Version>9.4.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.72" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.5" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="10.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.9.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
</ItemGroup>
</Project>
@@ -0,0 +1,29 @@
using CPATapi.Server.Interfaces;
using CPATapi.Server.Models;
using Microsoft.AspNetCore.Mvc;
using Dapper;
using Microsoft.Data.SqlClient;
namespace CPATapi.Server.Controllers;
[Route("[controller]")]
[ApiController]
public class AvailabilityController(IZeitConsensRepository zeitConsens) : ControllerBase
{
[HttpGet]
[Route("users")]
[ProducesResponseType<IEnumerable<string>>(StatusCodes.Status200OK)]
public async Task<IActionResult> GetUsers()
{
return Ok(await zeitConsens.GetUsersAsync());
}
[HttpGet]
[Route("{user}")]
[ProducesResponseType<Availability>(StatusCodes.Status200OK)]
public async Task<IActionResult> GetAvailability(string user)
{
var stampCount = (await zeitConsens.GetStampsAsync(user, DateTime.Now.Date, DateTime.Now.AddDays(1).Date)).Count();
return Ok(new Availability { User = user, LoggedIn = stampCount % 2 != 0 });
}
}
@@ -0,0 +1,30 @@
using CPATapi.Server.Interfaces;
using CPATapi.Server.Models;
using Microsoft.AspNetCore.Mvc;
namespace CPATapi.Server.Controllers;
[Route("[controller]")]
[ApiController]
public class CallerIdController(ITapiDirectoryRepository tapiDirectory) : ControllerBase
{
[HttpGet]
[Route("{number}")]
[ProducesResponseType<TapiContact>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> CallerIdAsync([FromRoute] string number)
{
if (number.Length >= 5)
{
var minMatch = number[^5..];
number = "%" + minMatch;
}
var result = await tapiDirectory.SearchByNumberAsync(number);
if (result != null)
{
return Ok(result);
}
return NotFound();
}
}
@@ -0,0 +1,21 @@
using CPATapi.Server.Interfaces;
using CPATapi.Server.Models;
using Microsoft.AspNetCore.Mvc;
namespace CPATapi.Server.Controllers;
[ApiController]
[Route("[controller]")]
public class ContactController(ILogger<ContactController> logger, ITapiDirectoryRepository tapiDirectory) : ControllerBase
{
private readonly ILogger<ContactController> _logger = logger;
[HttpGet]
[Produces("application/json")]
[ProducesResponseType<IEnumerable<TapiContact>>(StatusCodes.Status200OK)]
public async Task<IActionResult> GetAsync()
{
return Ok(await tapiDirectory.GetAllAsync());
}
}
@@ -0,0 +1,25 @@
using System.Text.RegularExpressions;
using CPATapi.Server.Interfaces;
using CPATapi.Server.Models;
using Microsoft.AspNetCore.Mvc;
namespace CPATapi.Server.Controllers;
[Route("[controller]")]
[ApiController]
public class SearchController(ITapiDirectoryRepository tapiDirectory) : ControllerBase
{
[HttpGet]
[ProducesResponseType<IEnumerable<TapiContact>>(StatusCodes.Status200OK)]
public async Task<IActionResult> SearchAsync([FromQuery] string query)
{
if (query == null)
{
return Ok(new List<TapiContact>());
}
var args = Regex.Split(query, "\\s").Where(s => !string.IsNullOrWhiteSpace(s)).Select(s => s.Trim()).ToArray();
return Ok(await tapiDirectory.SearchAsync(args));
}
}
+28
View File
@@ -0,0 +1,28 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["CPATapi.Server/CPATapi.Server.csproj", "CPATapi.Server/"]
RUN dotnet restore "CPATapi.Server/CPATapi.Server.csproj"
COPY . .
WORKDIR "/src/CPATapi.Server"
RUN dotnet build "./CPATapi.Server.csproj" -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./CPATapi.Server.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "CPATapi.Server.dll"]
@@ -0,0 +1,5 @@
namespace CPATapi.Server.Interfaces;
public interface IRepository
{
}
@@ -0,0 +1,10 @@
using CPATapi.Server.Models;
namespace CPATapi.Server.Interfaces;
public interface ITapiDirectoryRepository : IRepository
{
Task<IEnumerable<TapiContact>> SearchAsync(string[] args);
Task<TapiContact?> SearchByNumberAsync(string number);
Task<IEnumerable<TapiContact>> GetAllAsync();
}
@@ -0,0 +1,9 @@
using CPATapi.Server.Models;
namespace CPATapi.Server.Interfaces;
public interface IZeitConsensRepository : IRepository
{
Task<IEnumerable<string>> GetUsersAsync();
Task<IEnumerable<Stamp>> GetStampsAsync(string user, DateTime from, DateTime to);
}
@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
namespace CPATapi.Server.Models;
public class Availability
{
[JsonPropertyName("user")]
public string User { get; set; }
[JsonPropertyName("loggedIn")]
public bool LoggedIn { get; set; }
}
@@ -0,0 +1,7 @@
namespace CPATapi.Server.Models;
public class Stamp
{
public int MA_NR { get; set; }
public DateTime BU_BU { get; set; }
}
@@ -0,0 +1,10 @@
namespace CPATapi.Server.Models;
public class TapiContact
{
public string TD_ID { get; set; }
public string TD_NAME { get; set; }
public string TD_NUMBER { get; set; }
public string TD_NUMBER_TAPI { get; set; }
public string TD_MEDIUM { get; set; }
}
+43
View File
@@ -0,0 +1,43 @@
using CPATapi.Server.Interfaces;
using CPATapi.Server.Repository;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSerilog(config =>
{
config
.ReadFrom.Configuration(builder.Configuration)
.Enrich.WithClientIp()
.Enrich.WithCorrelationId()
.Enrich.WithRequestHeader("User-Agent")
.WriteTo.Console();
});
builder.Services.AddOpenApi();
builder.Services.AddTransient<ITapiDirectoryRepository, TapiDirectoryRepository>();
builder.Services.AddTransient<IZeitConsensRepository, ZeitConsensRepository>();
builder.Services.AddControllers();
var app = builder.Build();
app.UseSerilogRequestLogging();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.MapOpenApi();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/openapi/v1.json", "v1");
});
app.UseAuthorization();
app.MapControllers();
await app.RunAsync();
@@ -0,0 +1,40 @@
{
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger/index.html",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"CPATapiServer": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "availability/users",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://3cxtapi.cp-austria.at:5001;https://localhost:5001;http://localhost:5000"
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/contact",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": false
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:62406",
"sslPort": 0
}
}
}
@@ -0,0 +1,21 @@
using System.Data.Common;
using CPATapi.Server.Interfaces;
using Microsoft.Data.SqlClient;
namespace CPATapi.Server.Repository;
public class Repository: IRepository
{
private readonly IConfiguration _config;
protected Repository(IConfiguration config)
{
_config = config;
}
protected async Task<DbConnection> OpenAsync(string name)
{
var db = new SqlConnection(_config.GetConnectionString(name));
await db.OpenAsync();
return db;
}
}
@@ -0,0 +1,71 @@
using System.Text;
using CPATapi.Server.Interfaces;
using CPATapi.Server.Models;
using Dapper;
namespace CPATapi.Server.Repository;
internal class TapiDirectoryRepository(IConfiguration config) : Repository(config), ITapiDirectoryRepository
{
public async Task<IEnumerable<TapiContact>> SearchAsync(string[] args)
{
await using var con = await OpenAsync("Tapi");
var sql = new StringBuilder("""
SELECT TOP 10
TD_ID,
TD_NAME,
TD_NUMBER,
TD_NUMBER_TAPI,
TD_MEDIUM
FROM dbo.CP_TAPI_DIRECTORY
WHERE
""");
var first = true;
var dp = new DynamicParameters();
for (var i = 1; i <= args.Length; i++)
{
if (!first)
{
sql.AppendLine("AND");
}
first = false;
sql.AppendLine($"(TD_NAME LIKE @s{i} OR");
sql.AppendLine($" TD_NUMBER_TAPI LIKE @s{i})");
dp.Add($"s{i}", $"%{args[i-1]}%");
}
return await con.QueryAsync<TapiContact>(sql.ToString(), dp);
}
public async Task<TapiContact?> SearchByNumberAsync(string number)
{
await using var con = await OpenAsync("Tapi");
var result = await con.QueryFirstOrDefaultAsync<TapiContact>("""
SELECT
TD_ID
,TD_NAME
,TD_NUMBER
,TD_NUMBER_TAPI
,TD_MEDIUM
FROM dbo.CP_TAPI_DIRECTORY
WHERE TD_NUMBER_TAPI LIKE @number
""", new {number});
return result;
}
public async Task<IEnumerable<TapiContact>> GetAllAsync()
{
await using var con = await OpenAsync("Tapi");
return await con.QueryAsync<TapiContact>("""
SELECT
TD_ID
,TD_NAME
,TD_NUMBER
,TD_NUMBER_TAPI
,TD_MEDIUM
FROM dbo.CP_TAPI_DIRECTORY
""");
}
}
@@ -0,0 +1,34 @@
using CPATapi.Server.Interfaces;
using CPATapi.Server.Models;
using Dapper;
namespace CPATapi.Server.Repository;
internal class ZeitConsensRepository(IConfiguration config) : Repository(config), IZeitConsensRepository
{
public async Task<IEnumerable<string>> GetUsersAsync()
{
await using var con = await OpenAsync("ZeitConsens");
return await con.QueryAsync<string>("""
SELECT DISTINCT MA_USER_NAME
FROM dbo.MA_DATEN
WHERE MA_USER_NAME IS NOT NULL AND MA_USER_AKTIV = 1
ORDER BY MA_USER_NAME
""");
}
public async Task<IEnumerable<Stamp>> GetStampsAsync(string user, DateTime from, DateTime to)
{
await using var con = await OpenAsync("ZeitConsens");
return await con.QueryAsync<Stamp>("""
SELECT
MA_NR
,BU_BU
FROM dbo.BU
INNER JOIN dbo.MA_DATEN on MA_NR = BU_MA_NR
WHERE
MA_USER_NAME = @user AND
BU_BU >= @from AND BU_BU < @to
""", new { user, from, to });
}
}
@@ -0,0 +1,16 @@
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
},
"ConnectionStrings": {
"Tapi": "",
"ZeitConsens": ""
},
"AllowedHosts": "*"
}
+19
View File
@@ -0,0 +1,19 @@
@echo off
setlocal
cd /d %~dp0
docker build -t source.cp-austria.at/cpatrd/3cx_tapi:latest -f Dockerfile ..
if errorlevel 1 (
echo.
echo ERROR: Docker build failed!
exit /b 1
)
SET /P AREYOUSURE=Publish to source.cp-austria.at? (Y/[N])
IF /I "%AREYOUSURE%" NEQ "Y" GOTO END
docker push source.cp-austria.at/cpatrd/3cx_tapi:latest
:END
endlocal
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\CPATapi.Client\CPATapi.Client.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
</Project>
@@ -0,0 +1,45 @@
using Microsoft.Extensions.DependencyInjection;
namespace CPATapi.Client.Tests;
public class CPATapiClientTests
{
private IServiceScope CreateServices()
{
var services = new ServiceCollection();
services.AddCPATapiClient(((provider, client) =>
{
client.BaseAddress = new Uri("https://3cxtapi.cp-austria.at/");
}));
var sp = services.BuildServiceProvider();
return sp.CreateScope();
}
[Test]
public async Task TestContactsGet()
{
using var scope = CreateServices();
var client = scope.ServiceProvider.GetRequiredService<CPATapiClient>();
var contacts = await client.Contact.GetAsync();
Assert.That(contacts, Is.Not.Null);
Assert.That(contacts, Is.Not.Empty);
}
[Test]
public async Task TestAvailabilityGet()
{
using var scope = CreateServices();
var client = scope.ServiceProvider.GetRequiredService<CPATapiClient>();
var availability = await client.Availability["CPATRD"].GetAsync();
Assert.That(availability, Is.Not.Null);
}
[Test]
public async Task TestAvailabilityGetUnknown()
{
using var scope = CreateServices();
var client = scope.ServiceProvider.GetRequiredService<CPATapiClient>();
var availability = await client.Availability["Unknown"].GetAsync();
Assert.That(availability, Is.Not.Null);
}
}
-3
View File
@@ -1,3 +0,0 @@
declare module 'axios-userscript-adapter'
declare const GM: any
-9
View File
@@ -1,9 +0,0 @@
.tapi-search-result {
color: #000;
padding: 5px;
}
.tapi-search-result:hover, .tapi-search-result-selected {
background-color: #E7E6E6;
}
-157
View File
@@ -1,157 +0,0 @@
import './status.css';
import { axios } from './utils';
import { ZcStatus } from './zc-status';
declare function waitForKeyElements(selectorOrFunction: any, callback: any, waitOnce: boolean): any;
export class Status {
private _user: string;
private _enabled = false;
private _statusOn = 'menuAvailable';
private _statusOff = 'menuAway';
private _currentStatus: boolean = undefined;
public async showStatus(element: HTMLElement) {
this._user = await GM.getValue('tapi-zc-user', '');
this._enabled = await GM.getValue('tapi-zc-enabled', false);
this._statusOn = await GM.getValue('tapi-zc-on', 'menuAvailable');
this._statusOff = await GM.getValue('tapi-zc-off', 'menuAvailable');
console.log('tapi-zc-user', this._user, 'tapi-zc-enabled', this._enabled, 'tapi-zc-on', this._statusOn, 'tapi-zc-off', this._statusOff);
this.checkStatus();
waitForKeyElements("wc-account-menu > div > ul", (element: HTMLElement) => { this.addZcStatusPopup(element) }, true);
}
private async checkStatus() {
if (this._enabled) {
try {
var response = await axios.get<ZcStatus>('http://cpatapi.cpsrvweb2016.cp-austria.at/availability/' + encodeURIComponent(this._user));
if (response.status == 200) {
var status = response.data;
if (this._currentStatus !== status.loggedIn) {
this._currentStatus = status.loggedIn;
console.log('New status, loggedIn', this._currentStatus);
(document.getElementsByTagName("wcavatar")[0] as HTMLAnchorElement).click();
setTimeout(() => {
var statusId = this._currentStatus ? this._statusOn : this._statusOff;
(document.getElementById(statusId) as HTMLSpanElement).click();
}, 1000);
}
}
} catch (error) {
console.log(error);
}
setTimeout(() => this.checkStatus(), 30000);
}
}
private addZcStatusPopup(element: HTMLElement) {
var divider = document.createElement('li');
divider.classList.add('divider');
divider.classList.add('dropdown-divider');
element.appendChild(divider);
var menu = document.createElement('li');
element.appendChild(menu);
var link = document.createElement('a');
link.id = 'tapi-zc-button';
link.innerText = 'ZeitConsens';
link.classList.add('dropdown-item');
link.classList.add('d-flex');
link.onclick = () => {
document.getElementById('zc-modal').classList.toggle('show');
}
menu.appendChild(link);
var html =
'<div role="document" class="modal-dialog">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <h4 class="modal-title float-left">ZeitConsens Status</h4><button id="zc-btnClose" type="button" aria-label="Close" class="close float-right"><span aria-hidden="true">×</span></button>' +
' </div>' +
' <div class="modal-body">' +
' <div class="form-group">' +
' <label for="tapi-zc-user">Username</label>' +
' <input type="text" class="form-control" name="tapi-zc-user" id="tapi-zc-user">' +
' </div>' +
' <div class="form-group">' +
' <label for="tapi-zc-on">Signed in</label>' +
' <select id="tapi-zc-on" class="form-control">' +
' <option value="menuAvailable">Available</option>' +
' <option value="menuOutofoffice">Do Not Disturb</option>' +
' <option value="menuCustom1">Verfügbar DW</option>' +
' </select>' +
' </div>' +
' <div class="form-group">' +
' <label for="tapi-zc-off">Signed out</label>' +
' <select id="tapi-zc-off" class="form-control">' +
' <option value="menuAway">Away</option>' +
' <option value="menuOutofoffice">Do Not Disturb</option>' +
' </select>' +
' </div>' +
' <div class="checkbox">' +
' <label class="i-checks" for="tapi-zc-enabled">' +
' <input type="checkbox" id="tapi-zc-enabled">' +
' <i></i><span>Enabled</span>' +
' </label>'
' </div>';
' </div>' +
' <div class="modal-footer">' +
' <button id="zc-btnOk" type="button" class="btn btn-primary">OK </button>' +
' <button id="zc-btnCancel" type="button" class="btn btn-light">Cancel </button>' +
' </div>' +
' </div>' +
'</div>';
var modal = document.createElement('modal-container');
modal.id = 'zc-modal';
modal.classList.add('modal');
modal.classList.add('fade');
modal.innerHTML = html;
var body = document.getElementsByTagName('body')[0].appendChild(modal);
var btnClose = document.getElementById('zc-btnClose');
btnClose.onclick = () => {
document.getElementById('zc-modal').classList.toggle('show');
}
var zcUser = document.getElementById('tapi-zc-user') as HTMLInputElement;
zcUser.value = this._user;
zcUser.onchange = () => {
this._user = zcUser.value;
GM.setValue('tapi-zc-user', this._user);
console.log('tapi-zc-user', this._user);
this._currentStatus = undefined;
}
var zcEnabled = document.getElementById('tapi-zc-enabled') as HTMLInputElement;
zcEnabled.checked = this._enabled;
zcEnabled.onchange = () => {
this._enabled = zcEnabled.checked;
GM.setValue('tapi-zc-enabled', this._enabled);
console.log('tapi-zc-enabled', this._enabled);
this._currentStatus = undefined;
this.checkStatus();
}
var zcOn = document.getElementById('tapi-zc-on') as HTMLSelectElement;
zcOn.value = this._statusOn;
zcOn.onchange = () => {
this._statusOn = zcOn.value;
GM.setValue('tapi-zc-on', this._statusOn);
console.log('tapi-zc-on', this._statusOn);
this._currentStatus = undefined;
}
var zcOff = document.getElementById('tapi-zc-off') as HTMLSelectElement;
zcOff.value = this._statusOff;
zcOff.onchange = () => {
this._statusOff = zcOff.value;
GM.setValue('tapi-zc-off', this._statusOff);
console.log('tapi-zc-off', this._statusOff);
this._currentStatus = undefined;
}
}
}