mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-04 12:32:57 +00:00
Compare commits
5 Commits
@openstapp
...
192-fix-te
| Author | SHA1 | Date | |
|---|---|---|---|
|
180491cdd2
|
|||
|
|
689ac68be3 | ||
|
|
d36d9596fc | ||
|
|
8d50f669e7 | ||
|
|
dce08d9c03 |
9
.changeset/fair-monkeys-tickle.md
Normal file
9
.changeset/fair-monkeys-tickle.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
"@openstapps/node-builder": patch
|
||||||
|
"@openstapps/database": patch
|
||||||
|
"@openstapps/node-base": patch
|
||||||
|
"@openstapps/backend": patch
|
||||||
|
"@openstapps/app": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
pin alpine version to 3.18 and add healthchecks
|
||||||
@@ -9,4 +9,6 @@ ENV NODE_ENV=production
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
HEALTHCHECK --interval=10s --timeout=10s --start-period=10s --retries=12 CMD curl -s --fail --request POST --data '{}' --header 'Content-Type: application/json' http://localhost:3000/ >/dev/null || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["node", "app.js"]
|
ENTRYPOINT ["node", "app.js"]
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
"start:debug": "cross-env STAPPS_LOG_LEVEL=31 NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true node app.js",
|
"start:debug": "cross-env STAPPS_LOG_LEVEL=31 NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true node app.js",
|
||||||
"test": "pnpm run test:unit",
|
"test": "pnpm run test:unit",
|
||||||
"test:integration": "sh integration-test.sh",
|
"test:integration": "sh integration-test.sh",
|
||||||
"test:unit": "cross-env NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true STAPPS_LOG_LEVEL=0 mocha --exit"
|
"test:unit": "cross-env NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true STAPPS_LOG_LEVEL=0 c8 mocha"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elastic/elasticsearch": "8.4.0",
|
"@elastic/elasticsearch": "8.4.0",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
import {Logger, SMTP} from '@openstapps/logger';
|
import {Logger, SMTP} from '@openstapps/logger';
|
||||||
import {MailOptions} from 'nodemailer/lib/sendmail-transport';
|
import {MailOptions} from 'nodemailer/lib/sendmail-transport';
|
||||||
import Queue from 'promise-queue';
|
|
||||||
/**
|
/**
|
||||||
* A queue that can send mails in serial
|
* A queue that can send mails in serial
|
||||||
*/
|
*/
|
||||||
@@ -32,80 +32,45 @@ export class MailQueue {
|
|||||||
static readonly VERIFICATION_TIMEOUT = 5000;
|
static readonly VERIFICATION_TIMEOUT = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A queue that saves mails, before the transport is ready. When
|
* A promise that resolves when the last mail was sent
|
||||||
* the transport gets ready this mails are getting pushed in to
|
|
||||||
* the normal queue.
|
|
||||||
*/
|
*/
|
||||||
dryQueue: MailOptions[];
|
last?: Promise<string>;
|
||||||
|
|
||||||
/**
|
|
||||||
* A queue that saves mails, that are being sent in series
|
|
||||||
*/
|
|
||||||
queue: Queue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A counter for the number of verifications that failed
|
|
||||||
*/
|
|
||||||
verificationCounter: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a mail queue
|
* Creates a mail queue
|
||||||
* @param transport Transport which is used for sending mails
|
* @param transport Transport which is used for sending mails
|
||||||
*/
|
*/
|
||||||
constructor(private readonly transport: SMTP) {
|
constructor(private readonly transport: SMTP) {}
|
||||||
this.queue = new Queue(1);
|
|
||||||
|
|
||||||
// this queue saves all request when the transport is not ready yet
|
|
||||||
this.dryQueue = [];
|
|
||||||
|
|
||||||
this.verificationCounter = 0;
|
|
||||||
|
|
||||||
// if the transport can be verified it should check if it was done...
|
|
||||||
this.checkForVerification();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a mail into the queue so it gets send when the queue is ready
|
* Wait for the transport to be verified
|
||||||
* @param mail Information required for sending a mail
|
|
||||||
*/
|
*/
|
||||||
private async addToQueue(mail: MailOptions) {
|
private async waitForVerification() {
|
||||||
return this.queue.add<string>(() => this.transport.sendMail(mail));
|
for (let i = 0; i < MailQueue.MAX_VERIFICATION_ATTEMPTS; i++) {
|
||||||
}
|
if (this.transport.isVerified()) {
|
||||||
|
Logger.ok('Transport for mail queue was verified. We can send mails now');
|
||||||
/**
|
return;
|
||||||
* Verify the given transport
|
|
||||||
*/
|
|
||||||
private checkForVerification() {
|
|
||||||
if (this.verificationCounter >= MailQueue.MAX_VERIFICATION_ATTEMPTS) {
|
|
||||||
throw new Error('Failed to initialize the SMTP transport for the mail queue');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.transport.isVerified()) {
|
|
||||||
Logger.ok('Transport for mail queue was verified. We can send mails now');
|
|
||||||
// if the transport finally was verified send all our mails from the dry queue
|
|
||||||
for (const mail of this.dryQueue) {
|
|
||||||
void this.addToQueue(mail);
|
|
||||||
}
|
}
|
||||||
} else {
|
await new Promise(resolve => setTimeout(resolve, MailQueue.VERIFICATION_TIMEOUT));
|
||||||
this.verificationCounter++;
|
Logger.warn('Transport not verified yet. Trying to send mails here...');
|
||||||
setTimeout(() => {
|
|
||||||
Logger.warn('Transport not verified yet. Trying to send mails here...');
|
|
||||||
this.checkForVerification();
|
|
||||||
}, MailQueue.VERIFICATION_TIMEOUT);
|
|
||||||
}
|
}
|
||||||
|
throw new Error('Failed to initialize the SMTP transport for the mail queue');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push a mail into the queue so it gets send when the queue is ready
|
* Push a mail into the queue so it gets send when the queue is ready
|
||||||
* @param mail Information required for sending a mail
|
* @param mail Information required for sending a mail
|
||||||
*/
|
*/
|
||||||
public async push(mail: MailOptions) {
|
public async push(mail: MailOptions): Promise<string> {
|
||||||
if (this.transport.isVerified()) {
|
const previousQueue = this.last ?? this.waitForVerification();
|
||||||
await this.addToQueue(mail);
|
this.last = previousQueue.then(() =>
|
||||||
} else {
|
Promise.race([
|
||||||
// the transport has verification, but is not verified yet
|
this.transport.sendMail(mail),
|
||||||
// push to a dry queue which gets pushed to the real queue when the transport is verified
|
new Promise<string>((_, reject) =>
|
||||||
this.dryQueue.push(mail);
|
setTimeout(() => reject(new Error('Timeout')), MailQueue.VERIFICATION_TIMEOUT),
|
||||||
}
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
return this.last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ describe('MailQueue', async function () {
|
|||||||
clock.tick(MailQueue.VERIFICATION_TIMEOUT * (MailQueue.MAX_VERIFICATION_ATTEMPTS + 1));
|
clock.tick(MailQueue.VERIFICATION_TIMEOUT * (MailQueue.MAX_VERIFICATION_ATTEMPTS + 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(() => test()).to.throw();
|
expect(test).to.throw();
|
||||||
expect(loggerStub.callCount).to.be.equal(MailQueue.MAX_VERIFICATION_ATTEMPTS);
|
expect(loggerStub.callCount).to.be.equal(MailQueue.MAX_VERIFICATION_ATTEMPTS);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import {Elasticsearch} from '../../../src/storage/elasticsearch/elasticsearch.js
|
|||||||
import {bulk, DEFAULT_TEST_TIMEOUT, getTransport, getIndex} from '../../common.js';
|
import {bulk, DEFAULT_TEST_TIMEOUT, getTransport, getIndex} from '../../common.js';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import {backendConfig} from '../../../src/config.js';
|
import {backendConfig} from '../../../src/config.js';
|
||||||
import {readFile} from 'fs/promises';
|
|
||||||
import {
|
import {
|
||||||
ACTIVE_INDICES_ALIAS,
|
ACTIVE_INDICES_ALIAS,
|
||||||
getIndexUID,
|
getIndexUID,
|
||||||
@@ -50,6 +49,8 @@ import {
|
|||||||
} from '../../../src/storage/elasticsearch/util/index.js';
|
} from '../../../src/storage/elasticsearch/util/index.js';
|
||||||
import cron from 'node-cron';
|
import cron from 'node-cron';
|
||||||
import {query} from './query.js';
|
import {query} from './query.js';
|
||||||
|
import message from '@openstapps/core/test/resources/indexable/Message.1.json' assert {type: 'json'};
|
||||||
|
import book from '@openstapps/core/test/resources/indexable/Book.1.json' assert {type: 'json'};
|
||||||
|
|
||||||
use(chaiAsPromised);
|
use(chaiAsPromised);
|
||||||
|
|
||||||
@@ -60,21 +61,12 @@ function searchResponse<T>(...hits: SearchHit<T>[]): SearchResponse<T> {
|
|||||||
return {hits: {hits}, took: 0, timed_out: false, _shards: {total: 1, failed: 0, successful: 1}};
|
return {hits: {hits}, took: 0, timed_out: false, _shards: {total: 1, failed: 0, successful: 1}};
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = JSON.parse(
|
|
||||||
await readFile('node_modules/@openstapps/core/test/resources/indexable/Message.1.json', 'utf8'),
|
|
||||||
);
|
|
||||||
const book = JSON.parse(
|
|
||||||
await readFile('node_modules/@openstapps/core/test/resources/indexable/Book.1.json', 'utf8'),
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('Elasticsearch', function () {
|
describe('Elasticsearch', function () {
|
||||||
// increase timeout for the suite
|
// increase timeout for the suite
|
||||||
this.timeout(DEFAULT_TEST_TIMEOUT);
|
this.timeout(DEFAULT_TEST_TIMEOUT);
|
||||||
const sandbox = sinon.createSandbox();
|
const sandbox = sinon.createSandbox();
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('before');
|
|
||||||
sandbox.stub(fs, 'readFileSync').returns('{}');
|
sandbox.stub(fs, 'readFileSync').returns('{}');
|
||||||
});
|
});
|
||||||
after(function () {
|
after(function () {
|
||||||
@@ -269,7 +261,7 @@ describe('Elasticsearch', function () {
|
|||||||
return expect(es.init()).to.be.rejected;
|
return expect(es.init()).to.be.rejected;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should setup the monitoring if there is monitoring is set and mail queue is defined', function () {
|
it('should setup the monitoring if there is monitoring is set and mail queue is defined', async function () {
|
||||||
const config: SCConfigFile = {
|
const config: SCConfigFile = {
|
||||||
...backendConfig,
|
...backendConfig,
|
||||||
internal: {
|
internal: {
|
||||||
@@ -291,7 +283,7 @@ describe('Elasticsearch', function () {
|
|||||||
const cronSetupStub = sandbox.stub(cron, 'schedule');
|
const cronSetupStub = sandbox.stub(cron, 'schedule');
|
||||||
|
|
||||||
const es = new Elasticsearch(config, new MailQueue(getTransport(false) as unknown as SMTP));
|
const es = new Elasticsearch(config, new MailQueue(getTransport(false) as unknown as SMTP));
|
||||||
es.init();
|
await es.init();
|
||||||
|
|
||||||
expect(cronSetupStub.called).to.be.true;
|
expect(cronSetupStub.called).to.be.true;
|
||||||
});
|
});
|
||||||
@@ -445,7 +437,7 @@ describe('Elasticsearch', function () {
|
|||||||
_id: '',
|
_id: '',
|
||||||
_index: '',
|
_index: '',
|
||||||
_score: 0,
|
_score: 0,
|
||||||
_source: message as SCMessage,
|
_source: message.instance as SCMessage,
|
||||||
};
|
};
|
||||||
sandbox.stub(es.client, 'search').resolves(searchResponse(foundObject));
|
sandbox.stub(es.client, 'search').resolves(searchResponse(foundObject));
|
||||||
|
|
||||||
@@ -475,7 +467,7 @@ describe('Elasticsearch', function () {
|
|||||||
const object: SearchHit<SCMessage> = {
|
const object: SearchHit<SCMessage> = {
|
||||||
_id: '',
|
_id: '',
|
||||||
_index: oldIndex,
|
_index: oldIndex,
|
||||||
_source: message as SCMessage,
|
_source: message.instance as SCMessage,
|
||||||
};
|
};
|
||||||
sandbox.stub(es.client, 'search').resolves(searchResponse<SCMessage>(object));
|
sandbox.stub(es.client, 'search').resolves(searchResponse<SCMessage>(object));
|
||||||
sandbox.stub(es, 'prepareBulkWrite').resolves(index);
|
sandbox.stub(es, 'prepareBulkWrite').resolves(index);
|
||||||
@@ -489,7 +481,7 @@ describe('Elasticsearch', function () {
|
|||||||
sandbox.stub(es.client, 'create').resolves({result: 'not_found'} as CreateResponse);
|
sandbox.stub(es.client, 'create').resolves({result: 'not_found'} as CreateResponse);
|
||||||
|
|
||||||
await es.init();
|
await es.init();
|
||||||
return expect(es.post(message as SCMessage, bulk)).to.rejectedWith('creation');
|
return expect(es.post(message.instance as SCMessage, bulk)).to.rejectedWith('creation');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a new object', async function () {
|
it('should create a new object', async function () {
|
||||||
@@ -502,11 +494,11 @@ describe('Elasticsearch', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await es.init();
|
await es.init();
|
||||||
await es.post(message as SCMessage, bulk);
|
await es.post(message.instance as SCMessage, bulk);
|
||||||
|
|
||||||
expect(createStub.called).to.be.true;
|
expect(createStub.called).to.be.true;
|
||||||
expect(caughtParameter.document).to.be.eql({
|
expect(caughtParameter.document).to.be.eql({
|
||||||
...message,
|
...message.instance,
|
||||||
creation_date: caughtParameter.document.creation_date,
|
creation_date: caughtParameter.document.creation_date,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -527,7 +519,7 @@ describe('Elasticsearch', function () {
|
|||||||
_id: '',
|
_id: '',
|
||||||
_index: getIndex(),
|
_index: getIndex(),
|
||||||
_score: 0,
|
_score: 0,
|
||||||
_source: message as SCMessage,
|
_source: message.instance as SCMessage,
|
||||||
};
|
};
|
||||||
sandbox.stub(es.client, 'search').resolves(searchResponse());
|
sandbox.stub(es.client, 'search').resolves(searchResponse());
|
||||||
|
|
||||||
@@ -541,7 +533,7 @@ describe('Elasticsearch', function () {
|
|||||||
_id: '',
|
_id: '',
|
||||||
_index: getIndex(),
|
_index: getIndex(),
|
||||||
_score: 0,
|
_score: 0,
|
||||||
_source: message as SCMessage,
|
_source: message.instance as SCMessage,
|
||||||
};
|
};
|
||||||
sandbox.stub(es.client, 'search').resolves(searchResponse(object));
|
sandbox.stub(es.client, 'search').resolves(searchResponse(object));
|
||||||
// @ts-expect-error unused
|
// @ts-expect-error unused
|
||||||
@@ -564,13 +556,13 @@ describe('Elasticsearch', function () {
|
|||||||
_id: '123',
|
_id: '123',
|
||||||
_index: getIndex(),
|
_index: getIndex(),
|
||||||
_score: 0,
|
_score: 0,
|
||||||
_source: message as SCMessage,
|
_source: message.instance as SCMessage,
|
||||||
};
|
};
|
||||||
const objectBook: SearchHit<SCBook> = {
|
const objectBook: SearchHit<SCBook> = {
|
||||||
_id: '321',
|
_id: '321',
|
||||||
_index: getIndex(),
|
_index: getIndex(),
|
||||||
_score: 0,
|
_score: 0,
|
||||||
_source: book as SCBook,
|
_source: book.instance as SCBook,
|
||||||
};
|
};
|
||||||
const fakeEsAggregations = {
|
const fakeEsAggregations = {
|
||||||
'@all': {
|
'@all': {
|
||||||
|
|||||||
@@ -14,4 +14,6 @@ RUN chown elasticsearch:elasticsearch config/elasticsearch.yml
|
|||||||
|
|
||||||
USER elasticsearch
|
USER elasticsearch
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=10s --timeout=10s --start-period=60s --retries=12 CMD curl --fail -s http://localhost:9200/ >/dev/null || exit 1
|
||||||
|
|
||||||
CMD ["/usr/share/elasticsearch/bin/elasticsearch"]
|
CMD ["/usr/share/elasticsearch/bin/elasticsearch"]
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ discovery.type: "single-node"
|
|||||||
cluster.routing.allocation.disk.threshold_enabled: false
|
cluster.routing.allocation.disk.threshold_enabled: false
|
||||||
network.bind_host: 0.0.0.0
|
network.bind_host: 0.0.0.0
|
||||||
xpack.security.enabled: false
|
xpack.security.enabled: false
|
||||||
|
ingest.geoip.downloader.enabled: false
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
nginx &
|
nginx &
|
||||||
node ./lib/cli.js
|
node ./app.js
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ESM is not supported, and cts is not detected, so we use type-checked cjs instead.
|
|
||||||
/** @type {import('../src/common').ConfigFile} */
|
/** @type {import('../src/common').ConfigFile} */
|
||||||
const configFile = {
|
const configFile = {
|
||||||
activeVersions: ['1\\.0\\.\\d+', '2\\.0\\.\\d+'],
|
activeVersions: ['1\\.0\\.\\d+', '2\\.0\\.\\d+'],
|
||||||
@@ -46,7 +46,6 @@
|
|||||||
"@types/dockerode": "3.3.17",
|
"@types/dockerode": "3.3.17",
|
||||||
"@types/node": "18.15.3",
|
"@types/node": "18.15.3",
|
||||||
"@types/sha1": "1.1.3",
|
"@types/sha1": "1.1.3",
|
||||||
"config": "3.3.9",
|
|
||||||
"dockerode": "3.3.5",
|
"dockerode": "3.3.5",
|
||||||
"is-cidr": "4.0.2",
|
"is-cidr": "4.0.2",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
@@ -59,7 +58,6 @@
|
|||||||
"@openstapps/prettier-config": "workspace:*",
|
"@openstapps/prettier-config": "workspace:*",
|
||||||
"@openstapps/tsconfig": "workspace:*",
|
"@openstapps/tsconfig": "workspace:*",
|
||||||
"@types/chai": "4.3.5",
|
"@types/chai": "4.3.5",
|
||||||
"@types/config": "3.3.0",
|
|
||||||
"@types/dockerode": "3.3.17",
|
"@types/dockerode": "3.3.17",
|
||||||
"@types/mocha": "10.0.1",
|
"@types/mocha": "10.0.1",
|
||||||
"@types/mustache": "4.2.2",
|
"@types/mustache": "4.2.2",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import {Logger, SMTP} from '@openstapps/logger';
|
import {Logger, SMTP} from '@openstapps/logger';
|
||||||
import config from 'config';
|
|
||||||
import {existsSync} from 'fs';
|
import {existsSync} from 'fs';
|
||||||
|
|
||||||
// set transport on logger
|
// set transport on logger
|
||||||
@@ -163,7 +162,7 @@ ssl_stapling_verify on;`;
|
|||||||
/**
|
/**
|
||||||
* Config file
|
* Config file
|
||||||
*/
|
*/
|
||||||
export const configFile: ConfigFile = config.util.toObject();
|
export const configFile: ConfigFile = await import('../config/default.js').then(it => it.default);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if path is a specific file type
|
* Check if path is a specific file type
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @openstapps/app
|
# @openstapps/app
|
||||||
|
|
||||||
|
## 3.1.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Fix for Android users not being able to log in
|
||||||
|
|
||||||
## 3.1.1
|
## 3.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Creates a docker image with only the app as an executable unit
|
# Creates a docker image with only the app as an executable unit
|
||||||
# Dependencies need to be installed beforehand
|
# Dependencies need to be installed beforehand
|
||||||
# Needs to be build beforehand
|
# Needs to be build beforehand
|
||||||
FROM node:18-alpine
|
FROM node:18-alpine3.18
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY www/ /app/www
|
COPY www/ /app/www
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ include ':capacitor-splash-screen'
|
|||||||
project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/splash-screen/android')
|
project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/splash-screen/android')
|
||||||
|
|
||||||
include ':transistorsoft-capacitor-background-fetch'
|
include ':transistorsoft-capacitor-background-fetch'
|
||||||
project(':transistorsoft-capacitor-background-fetch').projectDir = new File('../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@1.0.2_@capacitor+core@5.5.0/node_modules/@transistorsoft/capacitor-background-fetch/android')
|
project(':transistorsoft-capacitor-background-fetch').projectDir = new File('../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.1.1_@capacitor+core@5.5.0/node_modules/@transistorsoft/capacitor-background-fetch/android')
|
||||||
|
|
||||||
include ':capacitor-secure-storage-plugin'
|
include ':capacitor-secure-storage-plugin'
|
||||||
project(':capacitor-secure-storage-plugin').projectDir = new File('../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.8.1_@capacitor+core@5.5.0/node_modules/capacitor-secure-storage-plugin/android')
|
project(':capacitor-secure-storage-plugin').projectDir = new File('../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.5.0/node_modules/capacitor-secure-storage-plugin/android')
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-check
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 StApps
|
* Copyright (C) 2022 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
@@ -12,9 +13,8 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import type {IconConfig} from './scripts/icon-config';
|
/** @type {import('./scripts/icon-config').IconConfig} */
|
||||||
|
const config = {
|
||||||
const config: IconConfig = {
|
|
||||||
inputPath: 'node_modules/material-symbols/material-symbols-rounded.woff2',
|
inputPath: 'node_modules/material-symbols/material-symbols-rounded.woff2',
|
||||||
outputPath: 'src/assets/icons.min.woff2',
|
outputPath: 'src/assets/icons.min.woff2',
|
||||||
htmlGlob: 'src/**/*.html',
|
htmlGlob: 'src/**/*.html',
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@openstapps/app",
|
"name": "@openstapps/app",
|
||||||
"description": "The generic app tailored to fulfill needs of German universities, written using Ionic Framework.",
|
"description": "The generic app tailored to fulfill needs of German universities, written using Ionic Framework.",
|
||||||
"version": "3.1.1",
|
"version": "3.1.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
|
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"build:prod": "ng build --configuration=production",
|
"build:prod": "ng build --configuration=production",
|
||||||
"build:stats": "ng build --configuration=production --stats-json",
|
"build:stats": "ng build --configuration=production --stats-json",
|
||||||
"changelog": "conventional-changelog -p angular -i src/assets/about/CHANGELOG.md -s -r 0",
|
"changelog": "conventional-changelog -p angular -i src/assets/about/CHANGELOG.md -s -r 0",
|
||||||
"check-icons": "ts-node-esm scripts/check-icon-correctness.ts",
|
"check-icons": "node scripts/check-icon-correctness.mjs",
|
||||||
"chromium:no-cors": "chromium --disable-web-security --user-data-dir=\".browser-data/chromium\"",
|
"chromium:no-cors": "chromium --disable-web-security --user-data-dir=\".browser-data/chromium\"",
|
||||||
"chromium:virtual-host": "chromium --host-resolver-rules=\"MAP mobile.app.uni-frankfurt.de:* localhost:8100\" --ignore-certificate-errors",
|
"chromium:virtual-host": "chromium --host-resolver-rules=\"MAP mobile.app.uni-frankfurt.de:* localhost:8100\" --ignore-certificate-errors",
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"licenses": "license-checker --json > src/assets/about/licenses.json && ts-node ./scripts/accumulate-licenses.ts && git add src/assets/about/licenses.json",
|
"licenses": "license-checker --json > src/assets/about/licenses.json && ts-node ./scripts/accumulate-licenses.ts && git add src/assets/about/licenses.json",
|
||||||
"lint": "ng lint && stylelint \"**/*.scss\"",
|
"lint": "ng lint && stylelint \"**/*.scss\"",
|
||||||
"lint:fix": "eslint --fix -c .eslintrc.json --ignore-path .eslintignore --ext .ts,.html src/ && stylelint --fix \"**/*.scss\"",
|
"lint:fix": "eslint --fix -c .eslintrc.json --ignore-path .eslintignore --ext .ts,.html src/ && stylelint --fix \"**/*.scss\"",
|
||||||
"minify-icons": "ts-node-esm scripts/minify-icon-font.ts",
|
"minify-icons": "node scripts/minify-icon-font.mjs",
|
||||||
"postinstall": "jetify && echo \"skipping jetify in production mode\"",
|
"postinstall": "jetify && echo \"skipping jetify in production mode\"",
|
||||||
"preview": "http-server www --p 8101 -o",
|
"preview": "http-server www --p 8101 -o",
|
||||||
"push": "git push && git push origin \"v$npm_package_version\"",
|
"push": "git push && git push origin \"v$npm_package_version\"",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-check
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 StApps
|
* Copyright (C) 2022 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
@@ -13,13 +14,14 @@
|
|||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import {omit} from '../src/app/_helpers/collections/omit';
|
import {omit, pickBy} from '@openstapps/collection-utils';
|
||||||
import {pickBy} from '../src/app/_helpers/collections/pick';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* accumulate and transform licenses based on two license files
|
* accumulate and transform licenses based on two license files
|
||||||
|
* @param {string} path
|
||||||
|
* @param {string} additionalLicensesPath
|
||||||
*/
|
*/
|
||||||
function accumulateFile(path: string, additionalLicensesPath: string) {
|
function accumulateFile(path, additionalLicensesPath) {
|
||||||
const packageJson = JSON.parse(fs.readFileSync('./package.json').toString());
|
const packageJson = JSON.parse(fs.readFileSync('./package.json').toString());
|
||||||
const dependencies = packageJson.dependencies;
|
const dependencies = packageJson.dependencies;
|
||||||
|
|
||||||
@@ -28,10 +30,9 @@ function accumulateFile(path: string, additionalLicensesPath: string) {
|
|||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path,
|
path,
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
Object.entries({
|
||||||
Object.entries<any>({
|
...pickBy(JSON.parse(fs.readFileSync(path).toString()), (_, key) => {
|
||||||
...pickBy(JSON.parse(fs.readFileSync(path).toString()), (_, key: string) => {
|
const parts = /** @type {string} */ (key).split('@');
|
||||||
const parts = key.split('@');
|
|
||||||
|
|
||||||
return dependencies[parts.slice(0, -1).join('@')] === parts[parts.length - 1];
|
return dependencies[parts.slice(0, -1).join('@')] === parts[parts.length - 1];
|
||||||
}),
|
}),
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-check
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 StApps
|
* Copyright (C) 2022 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
@@ -12,18 +13,18 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import fontkit, {Font} from 'fontkit';
|
import {openSync} from 'fontkit';
|
||||||
import config from '../icons.config';
|
import config from '../icons.config.mjs';
|
||||||
import {existsSync} from 'fs';
|
import {existsSync} from 'fs';
|
||||||
import {getUsedIconsHtml, getUsedIconsTS} from './gather-used-icons';
|
import {getUsedIconsHtml, getUsedIconsTS} from './gather-used-icons.mjs';
|
||||||
|
|
||||||
const commandName = '"npm run minify-icons"';
|
const commandName = '"npm run minify-icons"';
|
||||||
const originalFont = fontkit.openSync(config.inputPath);
|
const originalFont = openSync(config.inputPath);
|
||||||
if (!existsSync(config.outputPath)) {
|
if (!existsSync(config.outputPath)) {
|
||||||
console.error(`Minified font not found. Run ${commandName} first.`);
|
console.error(`Minified font not found. Run ${commandName} first.`);
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
const modifiedFont = fontkit.openSync(config.outputPath);
|
const modifiedFont = openSync(config.outputPath);
|
||||||
|
|
||||||
let success = true;
|
let success = true;
|
||||||
|
|
||||||
@@ -48,9 +49,9 @@ async function checkAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param {Record<string, string[]>} icons
|
||||||
*/
|
*/
|
||||||
function check(icons: Record<string, string[]>) {
|
function check(icons) {
|
||||||
for (const [purpose, iconSet] of Object.entries(icons)) {
|
for (const [purpose, iconSet] of Object.entries(icons)) {
|
||||||
for (const icon of iconSet) {
|
for (const icon of iconSet) {
|
||||||
if (!hasIcon(originalFont, icon)) {
|
if (!hasIcon(originalFont, icon)) {
|
||||||
@@ -65,8 +66,9 @@ function check(icons: Record<string, string[]>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param {import('fontkit').Font} font
|
||||||
|
* @param {string} icon
|
||||||
*/
|
*/
|
||||||
function hasIcon(font: Font, icon: string) {
|
function hasIcon(font, icon) {
|
||||||
return font.layout(icon).glyphs.some(it => it.isLigature);
|
return font.layout(icon).glyphs.some(it => it.isLigature);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-check
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 StApps
|
* Copyright (C) 2022 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
@@ -14,31 +15,33 @@
|
|||||||
*/
|
*/
|
||||||
import {glob} from 'glob';
|
import {glob} from 'glob';
|
||||||
import {readFileSync} from 'fs';
|
import {readFileSync} from 'fs';
|
||||||
import {matchPropertyContent, matchTagProperties} from '../src/app/util/ion-icon/icon-match';
|
import {matchPropertyContent, matchTagProperties} from '../src/app/util/ion-icon/icon-match.mjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @returns {Promise<Record<string, string[]>>}
|
||||||
*/
|
*/
|
||||||
export async function getUsedIconsHtml(pattern = 'src/**/*.html'): Promise<Record<string, string[]>> {
|
export async function getUsedIconsHtml(pattern = 'src/**/*.html') {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
(await glob(pattern))
|
(await glob(pattern))
|
||||||
.map(file => [
|
.map(file => [
|
||||||
file,
|
file,
|
||||||
(readFileSync(file, 'utf8')
|
/** @type {string[]} */ (
|
||||||
.match(matchTagProperties('ion-icon'))
|
readFileSync(file, 'utf8')
|
||||||
?.flatMap(match => {
|
.match(matchTagProperties('ion-icon'))
|
||||||
return match.match(matchPropertyContent(['name', 'md', 'ios']));
|
?.flatMap(match => {
|
||||||
})
|
return match.match(matchPropertyContent(['name', 'md', 'ios']));
|
||||||
.filter(it => !!it) as string[]) || [],
|
})
|
||||||
|
.filter(it => !!it)
|
||||||
|
) || [],
|
||||||
])
|
])
|
||||||
.filter(([, values]) => values.length > 0),
|
.filter(([, values]) => values.length > 0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @returns {Promise<Record<string, string[]>>}
|
||||||
*/
|
*/
|
||||||
export async function getUsedIconsTS(pattern = 'src/**/*.ts'): Promise<Record<string, string[]>> {
|
export async function getUsedIconsTS(pattern = 'src/**/*.ts') {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
(await glob(pattern))
|
(await glob(pattern))
|
||||||
.map(file => [file, readFileSync(file, 'utf8').match(/(?<=Icon`)[\w-]+(?=`)/g) || []])
|
.map(file => [file, readFileSync(file, 'utf8').match(/(?<=Icon`)[\w-]+(?=`)/g) || []])
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-check
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 StApps
|
* Copyright (C) 2022 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
@@ -12,18 +13,17 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import {openSync} from 'fontkit';
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
||||||
import fontkit from 'fontkit';
|
|
||||||
import {exec} from 'child_process';
|
import {exec} from 'child_process';
|
||||||
import config from '../icons.config';
|
import config from '../icons.config.mjs';
|
||||||
import {statSync} from 'fs';
|
import {statSync} from 'fs';
|
||||||
import {getUsedIconsHtml, getUsedIconsTS} from './gather-used-icons';
|
import {getUsedIconsHtml, getUsedIconsTS} from './gather-used-icons.mjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param {string[] | string} command
|
||||||
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
async function run(command: string[] | string): Promise<string> {
|
async function run(command) {
|
||||||
const fullCommand = Array.isArray(command) ? command.join(' ') : command;
|
const fullCommand = Array.isArray(command) ? command.join(' ') : command;
|
||||||
console.log(`>> ${fullCommand}`);
|
console.log(`>> ${fullCommand}`);
|
||||||
|
|
||||||
@@ -44,7 +44,8 @@ async function run(command: string[] | string): Promise<string> {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function minifyIconFont() {
|
async function minifyIconFont() {
|
||||||
const icons = new Set<string>();
|
/** @type {Set<string>} */
|
||||||
|
const icons = new Set();
|
||||||
|
|
||||||
for (const iconSet of [
|
for (const iconSet of [
|
||||||
...Object.values(config.additionalIcons || []),
|
...Object.values(config.additionalIcons || []),
|
||||||
@@ -57,9 +58,10 @@ async function minifyIconFont() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('Icons used:', [...icons.values()].sort());
|
console.log('Icons used:', [...icons.values()].sort());
|
||||||
const font = fontkit.openSync(config.inputPath);
|
const font = openSync(config.inputPath);
|
||||||
|
|
||||||
const glyphs: string[] = ['5f-7a', '30-39'];
|
/** @type {string[]} */
|
||||||
|
const glyphs = ['5f-7a', '30-39'];
|
||||||
for (const icon of icons) {
|
for (const icon of icons) {
|
||||||
const iconGlyphs = font.layout(icon).glyphs;
|
const iconGlyphs = font.layout(icon).glyphs;
|
||||||
if (iconGlyphs.length === 0) {
|
if (iconGlyphs.length === 0) {
|
||||||
@@ -70,7 +72,7 @@ async function minifyIconFont() {
|
|||||||
const codePoints = iconGlyphs
|
const codePoints = iconGlyphs
|
||||||
.flatMap(it => font.stringsForGlyph(it.id))
|
.flatMap(it => font.stringsForGlyph(it.id))
|
||||||
.flatMap(it => [...it])
|
.flatMap(it => [...it])
|
||||||
.map(it => it.codePointAt(0)!.toString(16));
|
.map(it => it.codePointAt(0).toString(16));
|
||||||
|
|
||||||
if (codePoints.length === 0) {
|
if (codePoints.length === 0) {
|
||||||
if (config.codePoints?.[icon]) {
|
if (config.codePoints?.[icon]) {
|
||||||
@@ -114,8 +116,10 @@ minifyIconFont();
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Bytes to respective units
|
* Bytes to respective units
|
||||||
|
* @param {number} value
|
||||||
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function toByteUnit(value: number): string {
|
function toByteUnit(value) {
|
||||||
if (value < 1024) {
|
if (value < 1024) {
|
||||||
return `${value}B`;
|
return `${value}B`;
|
||||||
} else if (value < 1024 * 1024) {
|
} else if (value < 1024 * 1024) {
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@openstapps/tsconfig",
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "CommonJS",
|
|
||||||
"moduleResolution": "Node"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -51,11 +51,7 @@ export class CapacitorRequestor extends Requestor {
|
|||||||
private async post<T>(url: string, data: any, headers: HttpHeaders) {
|
private async post<T>(url: string, data: any, headers: HttpHeaders) {
|
||||||
return CapacitorHttp.post({
|
return CapacitorHttp.post({
|
||||||
url,
|
url,
|
||||||
// Workaround for CapacitorHttp bug (JSONException when "x-www-form-urlencoded" text is provided)
|
data,
|
||||||
data:
|
|
||||||
headers['Content-Type'] === 'application/x-www-form-urlencoded'
|
|
||||||
? this.decodeURLSearchParams(data)
|
|
||||||
: data,
|
|
||||||
headers,
|
headers,
|
||||||
}).then((response: HttpResponse) => {
|
}).then((response: HttpResponse) => {
|
||||||
return response.data as T;
|
return response.data as T;
|
||||||
@@ -66,11 +62,7 @@ export class CapacitorRequestor extends Requestor {
|
|||||||
private async put<T>(url: string, data: any, headers: HttpHeaders) {
|
private async put<T>(url: string, data: any, headers: HttpHeaders) {
|
||||||
return CapacitorHttp.put({
|
return CapacitorHttp.put({
|
||||||
url,
|
url,
|
||||||
// Workaround for CapacitorHttp bug (JSONException when "x-www-form-urlencoded" text is provided)
|
data,
|
||||||
data:
|
|
||||||
headers['Content-Type'] === 'application/x-www-form-urlencoded'
|
|
||||||
? this.decodeURLSearchParams(data)
|
|
||||||
: data,
|
|
||||||
headers,
|
headers,
|
||||||
}).then((response: HttpResponse) => response.data as T);
|
}).then((response: HttpResponse) => response.data as T);
|
||||||
}
|
}
|
||||||
@@ -78,14 +70,4 @@ export class CapacitorRequestor extends Requestor {
|
|||||||
private async delete<T>(url: string, headers: HttpHeaders) {
|
private async delete<T>(url: string, headers: HttpHeaders) {
|
||||||
return CapacitorHttp.delete({url, headers}).then((response: HttpResponse) => response.data as T);
|
return CapacitorHttp.delete({url, headers}).then((response: HttpResponse) => response.data as T);
|
||||||
}
|
}
|
||||||
|
|
||||||
private decodeURLSearchParams(parameters: string): Record<string, unknown> {
|
|
||||||
const searchParameters = new URLSearchParams(parameters);
|
|
||||||
return Object.fromEntries(
|
|
||||||
[...searchParameters.keys()].map(k => [
|
|
||||||
k,
|
|
||||||
searchParameters.getAll(k).length === 1 ? searchParameters.get(k) : searchParameters.getAll(k),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-explicit-any */
|
||||||
import {CUSTOM_ELEMENTS_SCHEMA, DebugElement} from '@angular/core';
|
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
import {ActivatedRoute, RouterModule} from '@angular/router';
|
import {ActivatedRoute, RouterModule} from '@angular/router';
|
||||||
import {IonTitle} from '@ionic/angular';
|
|
||||||
import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
|
import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
|
||||||
import {sampleThingsMap} from '../../../_helpers/data/sample-things';
|
import {sampleThingsMap} from '../../../_helpers/data/sample-things';
|
||||||
import {DataRoutingModule} from '../data-routing.module';
|
import {DataRoutingModule} from '../data-routing.module';
|
||||||
import {DataModule} from '../data.module';
|
import {DataModule} from '../data.module';
|
||||||
import {DataProvider} from '../data.provider';
|
import {DataProvider} from '../data.provider';
|
||||||
import {DataDetailComponent} from './data-detail.component';
|
import {DataDetailComponent} from './data-detail.component';
|
||||||
import {By} from '@angular/platform-browser';
|
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
import {StorageProvider} from '../../storage/storage.provider';
|
import {StorageProvider} from '../../storage/storage.provider';
|
||||||
import {LoggerModule, NgxLoggerLevel} from 'ngx-logger';
|
import {LoggerModule, NgxLoggerLevel} from 'ngx-logger';
|
||||||
@@ -40,7 +38,6 @@ class TranslateFakeLoader implements TranslateLoader {
|
|||||||
describe('DataDetailComponent', () => {
|
describe('DataDetailComponent', () => {
|
||||||
let comp: DataDetailComponent;
|
let comp: DataDetailComponent;
|
||||||
let fixture: ComponentFixture<DataDetailComponent>;
|
let fixture: ComponentFixture<DataDetailComponent>;
|
||||||
let detailPage: DebugElement;
|
|
||||||
let dataProvider: DataProvider;
|
let dataProvider: DataProvider;
|
||||||
const sampleThing = sampleThingsMap.message[0];
|
const sampleThing = sampleThingsMap.message[0];
|
||||||
let translateService: TranslateService;
|
let translateService: TranslateService;
|
||||||
@@ -94,18 +91,12 @@ describe('DataDetailComponent', () => {
|
|||||||
spyOn(DataDetailComponent.prototype, 'getItem').and.callThrough();
|
spyOn(DataDetailComponent.prototype, 'getItem').and.callThrough();
|
||||||
fixture = TestBed.createComponent(DataDetailComponent);
|
fixture = TestBed.createComponent(DataDetailComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
detailPage = fixture.debugElement;
|
|
||||||
translateService.use('foo');
|
translateService.use('foo');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create component', () => expect(comp).toBeDefined());
|
it('should create component', () => expect(comp).toBeDefined());
|
||||||
|
|
||||||
it('should have appropriate title', async () => {
|
|
||||||
const title: DebugElement = detailPage.query(By.directive(IonTitle));
|
|
||||||
expect(title!.nativeElement.textContent).toBe('Foo');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get a data item', () => {
|
it('should get a data item', () => {
|
||||||
comp.getItem(sampleThing.uid, false);
|
comp.getItem(sampleThing.uid, false);
|
||||||
expect(DataDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false);
|
expect(DataDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false);
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ export class DataDetailComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
item?: SCThings | null = undefined;
|
item?: SCThings | null = undefined;
|
||||||
|
|
||||||
|
collapse = false;
|
||||||
|
|
||||||
@Input() inputItem?: SCThings;
|
@Input() inputItem?: SCThings;
|
||||||
|
|
||||||
@Input() isModal = false;
|
@Input() isModal = false;
|
||||||
|
|||||||
@@ -18,7 +18,12 @@
|
|||||||
<ion-buttons slot="start" *ngIf="!isModal">
|
<ion-buttons slot="start" *ngIf="!isModal">
|
||||||
<ion-back-button></ion-back-button>
|
<ion-back-button></ion-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>{{ 'data.detail.TITLE' | translate }}</ion-title>
|
<ion-title
|
||||||
|
*ngIf="item"
|
||||||
|
[style.opacity]="(collapse ? '1' : '0') + '!important'"
|
||||||
|
[style.translate]="collapse ? '0' : '0 10px'"
|
||||||
|
>{{ 'name' | thingTranslate: item }}</ion-title
|
||||||
|
>
|
||||||
<ion-buttons [slot]="isModal ? 'start' : 'primary'">
|
<ion-buttons [slot]="isModal ? 'start' : 'primary'">
|
||||||
<stapps-share-button *ngIf="item" [title]="'name' | thingTranslate: item"></stapps-share-button>
|
<stapps-share-button *ngIf="item" [title]="'name' | thingTranslate: item"></stapps-share-button>
|
||||||
<stapps-favorite-button *ngIf="item" [item]="$any(item)"></stapps-favorite-button>
|
<stapps-favorite-button *ngIf="item" [item]="$any(item)"></stapps-favorite-button>
|
||||||
@@ -31,7 +36,12 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ng-content select="[header]"></ng-content>
|
<ng-content select="[header]"></ng-content>
|
||||||
<ion-content parallax class="ion-no-padding">
|
<ion-content
|
||||||
|
parallax
|
||||||
|
class="ion-no-padding"
|
||||||
|
[scrollEvents]="true"
|
||||||
|
(ionScroll)="collapse = $any($event).detail.scrollTop > 50"
|
||||||
|
>
|
||||||
<ng-container [ngSwitch]="true">
|
<ng-container [ngSwitch]="true">
|
||||||
<ng-container *ngSwitchCase="!item && (isDisconnected | async)">
|
<ng-container *ngSwitchCase="!item && (isDisconnected | async)">
|
||||||
<div class="centered-message-container">
|
<div class="centered-message-container">
|
||||||
|
|||||||
@@ -19,3 +19,7 @@ ion-content > div {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-title {
|
||||||
|
transition: all 150ms ease;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-check
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 StApps
|
* Copyright (C) 2022 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
@@ -14,16 +15,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param {string} tag
|
||||||
*/
|
*/
|
||||||
export function matchTagProperties(tag: string) {
|
export function matchTagProperties(tag) {
|
||||||
return new RegExp(`(?<=<${tag})[\\s\\S]*?(?=>\\s*<\\/${tag}\\s*>)`, 'g');
|
return new RegExp(`(?<=<${tag})[\\s\\S]*?(?=>\\s*<\\/${tag}\\s*>)`, 'g');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param {string[]} properties
|
||||||
*/
|
*/
|
||||||
export function matchPropertyContent(properties: string[]) {
|
export function matchPropertyContent(properties) {
|
||||||
const names = properties.join('|');
|
const names = properties.join('|');
|
||||||
|
|
||||||
return new RegExp(`((?<=(${names})=")[\\w-]+(?="))|((?<=\\[(${names})]="')[\\w-]+(?='"))`, 'g');
|
return new RegExp(`((?<=(${names})=")[\\w-]+(?="))|((?<=\\[(${names})]="')[\\w-]+(?='"))`, 'g');
|
||||||
@@ -13,8 +13,7 @@
|
|||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
/* eslint-disable unicorn/no-null */
|
/* eslint-disable unicorn/no-null */
|
||||||
|
import {matchPropertyContent, matchTagProperties} from './icon-match.mjs';
|
||||||
import {matchPropertyContent, matchTagProperties} from './icon-match';
|
|
||||||
|
|
||||||
describe('matchTagProperties', function () {
|
describe('matchTagProperties', function () {
|
||||||
const regex = matchTagProperties('test');
|
const regex = matchTagProperties('test');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:18-alpine
|
FROM node:18-alpine3.18
|
||||||
|
|
||||||
RUN apk update && apk add git curl jq && mkdir -p /opt
|
RUN apk update && apk add git curl jq && mkdir -p /opt
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:18-alpine
|
FROM node:18-alpine3.18
|
||||||
|
|
||||||
RUN apk update && apk add git jq curl python3 build-base
|
RUN apk update && apk add git jq curl python3 build-base
|
||||||
|
|
||||||
|
|||||||
7
packages/es-mapping-generator/.mocharc.json
Normal file
7
packages/es-mapping-generator/.mocharc.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extension": ["ts"],
|
||||||
|
"require": "ts-node/register",
|
||||||
|
"reporter": "mocha-junit-reporter",
|
||||||
|
"reporter-option": ["mochaFile=coverage/report-junit.xml"],
|
||||||
|
"spec": ["test/**/*.spec.ts"]
|
||||||
|
}
|
||||||
@@ -18,8 +18,10 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"outDir": "./lib/",
|
"outDir": "./lib/",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
|
||||||
"target": "ES2020"
|
"target": "ES2020"
|
||||||
},
|
},
|
||||||
"exclude": ["./lib/", "./test/"]
|
"exclude": [
|
||||||
|
"./lib/",
|
||||||
|
"./test/"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user