Compare commits

...

26 Commits

Author SHA1 Message Date
bc0e219158 feat: improve search experience 2024-09-09 19:23:21 +02:00
3ac8c04765 fix: date range is now pipe has issues around boundaries 2024-08-06 17:43:59 +02:00
Rainer Killinger
5c260dd26b refactor: update android build dependencies for API level 34 2024-08-05 12:51:47 +00:00
Rainer Killinger
f2e1a6ddd5 docs: update changelogs for release
ci: publish release
2024-08-02 10:19:44 +02:00
Rainer Killinger
ccacfbf710 fix: publish-packages script 2024-08-02 10:18:57 +02:00
Rainer Killinger
b249b329f7 refactor: use development backend for non production environment 2024-08-01 17:44:21 +02:00
Jovan Krunić
7afc24f1bc fix: use major version config from storage when offline
Closes #218
2024-08-01 17:44:20 +02:00
Rainer Killinger
7276525dfa fix: app-builder image build due to nodejs versions <18.19 not being supported by angular 17 2024-08-01 15:24:59 +02:00
Jovan Krunić
c9e881582f refactor: set production version of the backend 2024-07-19 15:20:06 +02:00
Jovan Krunić
df681de083 feat: show title if available on library account profile
Closes #215
2024-07-19 15:20:06 +02:00
802a7a3fa7 fix: docs generation 2024-07-19 15:20:06 +02:00
Jovan Krunić
343d03e647 feat: library account adjustments
Closes #214
2024-07-19 15:20:06 +02:00
727b92911e fix: list item layout broken 2024-07-19 15:20:06 +02:00
11bc987807 fix: elasticsearch integration spams errors 2024-07-19 15:20:06 +02:00
Rainer Killinger
b8faae5988 docs: update changelogs for release
ci: publish release
2024-06-28 17:39:39 +02:00
67ab1fd613 fix: geo.point has wrong mapping 2024-06-28 17:25:32 +02:00
Rainer Killinger
142079bf0e refactor: set app backend to dev version 2024-06-28 12:02:59 +02:00
Rainer Killinger
5050ac90eb docs: update changelogs for release
ci: publish release
2024-06-28 09:28:39 +02:00
Rainer Killinger
688bc5f2e7 refactor: add changeset 2024-06-28 09:03:54 +02:00
Rainer Killinger
ad174dd7d7 refactor: use static map files from backend 2024-06-28 08:59:08 +02:00
Rainer Killinger
26e654f5b8 fix: app license overview 2024-06-27 14:57:24 +02:00
Rainer Killinger
5439484a90 fix: changelog typo 2024-06-27 12:06:52 +02:00
Rainer Killinger
68f3366a27 refactor: adjustments for recent PAIA changes 2024-06-27 09:23:55 +00:00
Jovan Krunić
dea9a82105 fix: do not fetch remote configuration if offline
Closes #206
2024-06-17 09:55:29 +00:00
39d2801114 feat: store id cards 2024-06-12 13:51:46 +02:00
Rainer Killinger
341b209092 refactor: display id-cards in their own modal 2024-06-05 11:07:30 +00:00
102 changed files with 5049 additions and 3004 deletions

View File

@@ -1,5 +1,31 @@
# @openstapps/backend
## 3.3.2
### Patch Changes
- @openstapps/core@3.3.2
- @openstapps/core-tools@3.3.2
## 3.3.1
### Patch Changes
- 67ab1fd6: fix for geo.point mapping
## 3.3.0
### Minor Changes
- 688bc5f2: v3.3.0 changes
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/core@3.3.0
- @openstapps/core-tools@3.3.0
- @openstapps/logger@3.0.0
## 3.2.0
### Minor Changes

View File

@@ -8,8 +8,7 @@ const config = {
database: {
name: 'elasticsearch',
query: {
minMatch: '60%',
queryType: 'query_string',
fields: ["name"]
},
},
},

View File

@@ -39,7 +39,7 @@ const boostings = {
type: SCThingType.AcademicEvent,
},
{
factor: 1.6,
factor: 2,
type: SCThingType.Building,
},
{
@@ -85,7 +85,7 @@ const boostings = {
],
place: [
{
factor: 2,
factor: 3,
type: SCThingType.Building,
},
{

View File

@@ -17,12 +17,21 @@ const config = {
name: 'elasticsearch',
version: '8.4.2',
query: {
minMatch: '75%',
queryType: 'dis_max',
matchBoosting: 1.3,
fuzziness: 'AUTO',
cutoffFrequency: 0,
tieBreaker: 0,
type: 'best_fields',
fields: [
'identifiers^20',
'name^10',
'translations.*.name^10',
'alternateNames^10',
'translations.*.alternateNames^10',
'description^2',
'translations.*.description^2',
'categories^5',
],
},
searchAsYouTypeQuery: {
type: 'phrase_prefix',
fields: ['name.completion', 'name.completion._2gram', 'name.completion._3gram'],
},
},
},

View File

@@ -2,7 +2,7 @@
The Goethe-Uni App got even better!
## Completelty new map view
## Completely new map view
We overhauled the map to offer you a clearer and faster and overview.

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/backend",
"description": "A reference implementation for a StApps backend",
"version": "3.2.0",
"version": "3.3.2",
"private": true,
"type": "module",
"license": "AGPL-3.0-only",

View File

@@ -20,6 +20,8 @@ import {
IndicesGetAliasResponse,
SearchHit,
SearchResponse,
SearchTermSuggest,
SearchTermSuggestOption,
} from '@elastic/elasticsearch/lib/api/types.js';
import {SCConfigFile, SCSearchQuery, SCSearchResponse, SCThings, SCUuid} from '@openstapps/core';
import {Logger} from '@openstapps/logger';
@@ -47,6 +49,9 @@ import {
import {noUndefined} from './util/no-undefined.js';
import {retryCatch, RetryOptions} from './util/retry.js';
import {Feature, Point, Polygon} from 'geojson';
import {parseSuggestions} from './util/parse-suggestions.js';
import {buildScoringFunctions} from './query/boost/scoring-functions.js';
import {buildFilter} from './query/filter.js';
/**
* A database interface for elasticsearch
@@ -355,6 +360,39 @@ export class Elasticsearch implements Database {
throw new Error('You tried to PUT an non-existing object. PUT is only supported on existing objects.');
}
public async searchAsYouType(parameters: SCSearchQuery): Promise<SCSearchResponse> {
const result = await this.client.search({
_source: 'name',
query: {
function_score: {
functions: buildScoringFunctions(this.config.internal.boostings, parameters.context),
query: {
bool: {
must: {
multi_match: {
query: parameters.query,
type: 'bool_prefix',
fields: ['name.completion', 'name.completion._2gram', 'name.completion._3gram'],
},
},
should: [],
filter: parameters.filter === undefined ? undefined : buildFilter(parameters.filter),
},
},
score_mode: 'max',
boost_mode: 'multiply',
},
},
index: ACTIVE_INDICES_ALIAS,
allow_no_indices: true,
size: 5,
});
const suggestions = result.hits.hits.map(it => (it._source as any).name);
console.log(suggestions);
console.log(result.took);
}
/**
* Search all indexed data
* @param parameters search query
@@ -364,18 +402,23 @@ export class Elasticsearch implements Database {
throw new TypeError('Database is undefined. You have to configure the query build');
}
const esConfig: ElasticsearchConfig = {
name: this.config.internal.database.name as 'elasticsearch',
version: this.config.internal.database.version as string,
query: this.config.internal.database.query as
| ElasticsearchQueryDisMaxConfig
| ElasticsearchQueryQueryStringConfig
| undefined,
};
const esConfig = this.config.internal.database as object as ElasticsearchConfig;
const response: SearchResponse<SCThings> = await this.client.search({
aggs: aggregations,
query: buildQuery(parameters, this.config, esConfig),
suggest:
parameters.query === undefined
? undefined
: {
text: parameters.query,
terms: {
term: {
field: 'name',
suggest_mode: 'missing',
},
},
},
from: parameters.from,
index: ACTIVE_INDICES_ALIAS,
allow_no_indices: true,
@@ -395,6 +438,7 @@ export class Elasticsearch implements Database {
response.aggregations === undefined
? []
: parseAggregations(response.aggregations as Record<AggregateName, AggregationsMultiTermsBucket>),
suggestions: response.suggest === undefined ? undefined : parseSuggestions(response.suggest),
pagination: {
count: response.hits.hits.length,
offset: typeof parameters.from === 'number' ? parameters.from : 0,

View File

@@ -25,7 +25,7 @@ export function buildDistanceFilter(
): QueryDslSpecificQueryContainer<'geo_distance'> {
const geoObject: QueryDslGeoDistanceQuery = {
distance: `${filter.arguments.distance}m`,
[`${filter.arguments.field}.point.coordinates`]: {
[`${filter.arguments.field}.point`]: {
lat: filter.arguments.position[1],
lon: filter.arguments.position[0],
},

View File

@@ -30,84 +30,21 @@ export const buildQuery = function buildQuery(
defaultConfig: SCConfigFile,
elasticsearchConfig: ElasticsearchConfig,
): QueryDslQueryContainer {
// if config provides a minMatch parameter, we use query_string instead of a match query
let query;
if (elasticsearchConfig.query === undefined) {
query = {
query_string: {
analyzer: 'search_german',
default_field: 'name',
minimum_should_match: '90%',
query: typeof parameters.query === 'string' ? parameters.query : '*',
},
};
} else if (elasticsearchConfig.query.queryType === 'query_string') {
query = {
query_string: {
analyzer: 'search_german',
default_field: 'name',
minimum_should_match: elasticsearchConfig.query.minMatch,
query: typeof parameters.query === 'string' ? parameters.query : '*',
},
};
} else if (elasticsearchConfig.query.queryType === 'dis_max') {
if (typeof parameters.query === 'string' && parameters.query !== '*') {
query = {
dis_max: {
boost: 1.2,
queries: [
{
match: {
name: {
boost: elasticsearchConfig.query.matchBoosting,
fuzziness: elasticsearchConfig.query.fuzziness,
query: parameters.query,
},
},
},
{
query_string: {
default_field: 'name',
minimum_should_match: elasticsearchConfig.query.minMatch,
query: parameters.query,
},
},
],
tie_breaker: elasticsearchConfig.query.tieBreaker,
},
};
}
} else {
throw new Error(
'Unsupported query type. Check your config file and reconfigure your elasticsearch query',
);
}
const functionScoreQuery: QueryDslQueryContainer = {
return {
function_score: {
functions: buildScoringFunctions(defaultConfig.internal.boostings, parameters.context),
query: {
bool: {
minimum_should_match: 0, // if we have no should, nothing can match
must: [],
must:
parameters.query === undefined || parameters.query === '' || parameters.query === '*'
? {match_all: {}}
: {multi_match: {...elasticsearchConfig.query, query: parameters.query}},
should: [],
filter: parameters.filter === undefined ? undefined : buildFilter(parameters.filter),
},
},
score_mode: 'multiply',
score_mode: 'max',
boost_mode: 'multiply',
},
};
const mustMatch = functionScoreQuery.function_score?.query?.bool?.must;
if (Array.isArray(mustMatch)) {
if (query !== undefined) {
mustMatch.push(query);
}
if (parameters.filter !== undefined) {
mustMatch.push(buildFilter(parameters.filter));
}
}
return functionScoreQuery;
};

View File

@@ -25,7 +25,7 @@ export function buildDistanceSort(sort: SCDistanceSort): SortOptions {
mode: 'avg',
order: sort.order,
unit: 'm',
[`${sort.arguments.field}.point.coordinates`]: {
[`${sort.arguments.field}.point`]: {
lat: sort.arguments.position[1],
lon: sort.arguments.position[0],
},

View File

@@ -13,68 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* A configuration for using the Dis Max Query
*
* See https://www.elastic.co/guide/en/elasticsearch/reference/5.5/query-dsl-dis-max-query.html for further
* explanation of what the parameters mean
*/
export interface ElasticsearchQueryDisMaxConfig {
/**
* Relative (to a total number of documents) or absolute number to exclude meaningless matches that frequently appear
*/
cutoffFrequency: number;
/**
* The maximum allowed Levenshtein Edit Distance (or number of edits)
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/common-options.html#fuzziness
*/
fuzziness: number | string;
/**
* Increase the importance (relevance score) of a field
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/mapping-boost.html
*/
matchBoosting: number;
/**
* Minimal number (or percentage) of words that should match in a query
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-minimum-should-match.html
*/
minMatch: string;
/**
* Type of the query - in this case 'dis_max' which is a union of its subqueries
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-dis-max-query.html
*/
queryType: 'dis_max';
/**
* Changes behavior of default calculation of the score when multiple results match
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-multi-match-query.html#tie-breaker
*/
tieBreaker: number;
}
/**
* A configuration for using Query String Query
*
* See https://www.elastic.co/guide/en/elasticsearch/reference/5.5/query-dsl-query-string-query.html for further
* explanation of what the parameters mean
*/
export interface ElasticsearchQueryQueryStringConfig {
/**
* Minimal number (or percentage) of words that should match in a query
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-minimum-should-match.html
*/
minMatch: string;
/**
* Type of the query - in this case 'query_string' which uses a query parser in order to parse content
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-query-string-query.html
*/
queryType: 'query_string';
}
import {QueryDslMultiMatchQuery} from '@elastic/elasticsearch/lib/api/types.js';
/**
* An config file for the elasticsearch database interface
@@ -105,7 +44,12 @@ export interface ElasticsearchConfig {
/**
* Configuration for using queries
*/
query?: ElasticsearchQueryDisMaxConfig | ElasticsearchQueryQueryStringConfig;
query: Omit<QueryDslMultiMatchQuery, 'query'>;
/**
*
*/
searchAsYouTypeQuery: Omit<QueryDslMultiMatchQuery, 'query'>;
/**
* Version of the used elasticsearch

View File

@@ -0,0 +1,28 @@
import {
SearchSuggest,
SearchTermSuggest,
SearchTermSuggestOption,
SuggestionName,
} from '@elastic/elasticsearch/lib/api/types.js';
import {SCSearchSuggestions} from '@openstapps/core';
/**
* Parse ES Suggestions to SC Search Suggestions
*/
export function parseSuggestions(suggest: Record<SuggestionName, SearchSuggest[]>): SCSearchSuggestions {
const termsSuggestions =
suggest.terms === undefined
? []
: (suggest.terms as SearchTermSuggest[])
?.map(
({text, options}) =>
[
text,
(options as SearchTermSuggestOption[] | undefined)?.map(({text}) => text) ?? [],
] as const,
)
.filter(([, suggestions]) => suggestions.length > 0) ?? [];
return {
terms: termsSuggestions.length === 0 ? undefined : Object.fromEntries(termsSuggestions),
};
}

View File

@@ -466,7 +466,7 @@ describe('Query', function () {
const expectedFilter: QueryDslSpecificQueryContainer<'geo_distance'> = {
geo_distance: {
'distance': '1000m',
'geo.point.coordinates': {
'geo.point': {
lat: 8.123,
lon: 50.123,
},
@@ -636,7 +636,7 @@ describe('Query', function () {
'mode': 'avg',
'order': 'desc',
'unit': 'm',
'geo.point.coordinates': {
'geo.point': {
lat: 50.123,
lon: 8.123,
},

View File

@@ -1,5 +1,11 @@
# @openstapps/tsconfig
## 3.3.0
### Minor Changes
- 688bc5f2: v3.3.0 changes
## 3.0.0
### Major Changes
@@ -30,7 +36,7 @@
```js
#!/usr/bin/env node
import './lib/app.js';
import "./lib/app.js";
```
- 64caebaf: Migrate to ESM
@@ -105,7 +111,7 @@
```js
#!/usr/bin/env node
import './lib/app.js';
import "./lib/app.js";
```
- 64caebaf: Migrate to ESM

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/tsconfig",
"description": "The tsconfig for the openstapps project",
"version": "3.0.0",
"version": "3.3.0",
"type": "commonjs",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/eslint-config.git",

View File

@@ -1,12 +1,28 @@
# @openstapps/minimal-connector
## 3.3.2
### Patch Changes
- @openstapps/core@3.3.2
- @openstapps/api@3.3.2
## 3.3.0
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/api@3.3.0
- @openstapps/core@3.3.0
- @openstapps/logger@3.0.0
## 3.2.0
### Patch Changes
- Updated dependencies [912ae422]
- @openstapps/core@4.0.0
- @openstapps/api@4.0.0
- @openstapps/core@3.2.0
- @openstapps/api@3.2.0
- @openstapps/logger@3.0.0
## 3.1.1

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/minimal-connector",
"description": "This is a minimal connector which serves as an example",
"version": "3.2.0",
"version": "3.3.2",
"private": true,
"type": "module",
"license": "GPL-3.0-only",

View File

@@ -1,13 +1,33 @@
# @openstapps/minimal-plugin
## 3.3.2
### Patch Changes
- @openstapps/core@3.3.2
- @openstapps/core-tools@3.3.2
- @openstapps/api-plugin@3.3.2
- @openstapps/api@3.3.2
## 3.3.0
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/api@3.3.0
- @openstapps/core@3.3.0
- @openstapps/api-plugin@3.3.0
- @openstapps/core-tools@3.3.0
- @openstapps/logger@3.0.0
## 3.2.0
### Patch Changes
- Updated dependencies [912ae422]
- @openstapps/core@4.0.0
- @openstapps/api@4.0.0
- @openstapps/api-plugin@4.0.0
- @openstapps/core@3.2.0
- @openstapps/api@3.2.0
- @openstapps/api-plugin@3.2.0
- @openstapps/core-tools@3.0.0
- @openstapps/logger@3.0.0

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/minimal-plugin",
"description": "Minimal Plugin",
"version": "3.2.0",
"version": "3.3.2",
"private": true,
"type": "module",
"license": "GPL-3.0-only",

View File

@@ -1,5 +1,26 @@
# @openstapps/app
## 3.3.2
### Patch Changes
- 7afc24f1: fixed config version comparison
- @openstapps/core@3.3.2
- @openstapps/api@3.3.2
## 3.3.0
### Minor Changes
- 688bc5f2: v3.3.0 changes
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/api@3.2.0
- @openstapps/core@3.2.0
- @openstapps/collection-utils@3.0.0
## 3.2.0
### Patch Changes

View File

@@ -13,25 +13,25 @@ The StApps 1.x.x (legacy app, but current app in stores) is written using Ionic
There are (`npm`) scripts defined to get the app running as quickly as possible. Those scripts (shortcuts for docker commands) are called using the syntax `npm run + <script-name>`. So we have the following commands available:
```
```sh
npm run docker:pull
```
which pulls the up-to-date image ([Dockerfile](Dockerfile)) which contains all the tools needed for building, serving and deploying the app.
```
```sh
npm run docker:enter
```
which enters the container on docker builder image, where we can run `npm install` (to install the required npm packages) and `npm build` (to build the app: convert into executable files), but also any other arbitrary commands with the tools available in the docker image.
```
```sh
npm run docker:build
```
which runs `npm install` (to install the required npm packages) and `npm build` (to build the app: convert into executable files) in the docker container which runs on the docker builder image.
```
```sh
npm run docker:serve
```
@@ -39,7 +39,7 @@ which serves the app for running it in the browser. It basically runs `ionic ser
## How to build and start the app using the default backend?
```
```sh
npm run build
npm run start
```
@@ -86,52 +86,98 @@ addToIonicDB(
You'll need to run _Chromium_ using
```shell
```sh
pnpm chromium:no-cors
```
### Help, I can't log in!
Login services will often block hosts not coming from the production
server. You can circumvent this locally by using the `:virtual-host`
scripts:
server.
```shell
#### Web
On the web you can circumvent this locally by using the `:virtual-host` scripts:
```sh
# Start the dev server on mobile.app.uni-frankfurt.de
pnpm start:virtual-host
# Run chromium with flags that redirect mobile.app.uni-frankfurt.de to localhost:8100
pnpm chromium:virtual-host
```
#### Android
On Android you will need to change the `custom_url_scheme` values
to `de.unifrankfurt.app` in the following files:
- `android/app/src/main/res/values/strings.xml`
- `src/environment/environment.ts`
Then start the app normally as you would
```sh
pnpm run:android
```
**This alone will not make auth work**, only the login flow.
If you need to test login, you have to disable live reload:
```sh
pnpm ionic capacitor run android
```
_**CAUTION:** a remote chrome debugging session can in some
cases hijack the device's ADB connection. If the connection
fails for no obvious reason, close chrome and uninstall the
app, then try again._
#### iOS
On Android you will need to change the `custom_url_scheme` value in
`src/environment/environment.ts` as well as the `CFBundleURLTypes`
in `ios/App/App/Info.plist`.
- make sure to remove any `Info.plist.orig` as capacitor might override
the modified `Info.plist` with that.
- make sure you have a valid device in XCode (Simulator or real device).
After that, run
```sh
pnpm run:ios
```
### Running the app
Install the npm packages needed for running the app (as for any other node project which uses npm):
```
```sh
npm install
```
Check the code for linter issues:
```
```sh
npm run lint
```
Automatically fix linter issues (those where autofix is possible):
```
```sh
npm run lint:fix
```
Build the app (transpile etc.):
```
```sh
npm run build
```
Open the app in the browser:
```
```sh
ionic serve
```
@@ -139,7 +185,7 @@ ionic serve
Run the app for testing on an android device (with live reload in the webview / device, when files are changed):
```
```sh
npm run build # if needed
npm run resources:android # generate needed resources (icons and splashscreens)
npm run docker:run:android # runs "ionic capacitor run android --livereload --external" on a selected device
@@ -159,7 +205,7 @@ Besides that, it is possible to monitor processes (and so the processes related
Build the (debug) app for testing on an android device (creates an APK file in the android's build outputs path):
```
```sh
npm run docker:build:android
```
@@ -169,13 +215,13 @@ The mentioned `docker:*:android` npm commands are executed in a docker container
Execute unit tests:
```
```sh
npm test
```
Execute e2e tests:
```
```sh
npm run e2e
```

View File

@@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android {
namespace "de.anyschool.app"
compileSdkVersion rootProject.ext.compileSdkVersion
compileSdk rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "de.anyschool.app"
minSdkVersion rootProject.ext.minSdkVersion

View File

@@ -9,6 +9,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-community-screen-brightness')
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-clipboard')
@@ -21,6 +22,7 @@ dependencies {
implementation project(':capacitor-local-notifications')
implementation project(':capacitor-network')
implementation project(':capacitor-preferences')
implementation project(':capacitor-screen-orientation')
implementation project(':capacitor-share')
implementation project(':capacitor-splash-screen')
implementation project(':transistorsoft-capacitor-background-fetch')

View File

@@ -1,4 +1,8 @@
[
{
"pkg": "@capacitor-community/screen-brightness",
"classpath": "com.elylucas.capscreenbrightness.ScreenBrightnessPlugin"
},
{
"pkg": "@capacitor/app",
"classpath": "com.capacitorjs.plugins.app.AppPlugin"
@@ -47,6 +51,10 @@
"pkg": "@capacitor/preferences",
"classpath": "com.capacitorjs.plugins.preferences.PreferencesPlugin"
},
{
"pkg": "@capacitor/screen-orientation",
"classpath": "com.capacitorjs.plugins.screenorientation.ScreenOrientationPlugin"
},
{
"pkg": "@capacitor/share",
"classpath": "com.capacitorjs.plugins.share.SharePlugin"

View File

@@ -7,8 +7,8 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.0.0'
classpath 'com.google.gms:google-services:4.3.15'
classpath 'com.android.tools.build:gradle:8.2.1'
classpath 'com.google.gms:google-services:4.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@@ -1,51 +1,57 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/android/capacitor')
project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/android/capacitor')
include ':capacitor-community-screen-brightness'
project(':capacitor-community-screen-brightness').projectDir = new File('../../../node_modules/.pnpm/@capacitor-community+screen-brightness@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor-community/screen-brightness/android')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/app/android')
project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/app/android')
include ':capacitor-browser'
project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@5.1.0_@capacitor+core@5.5.0/node_modules/@capacitor/browser/android')
project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/browser/android')
include ':capacitor-clipboard'
project(':capacitor-clipboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+clipboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/clipboard/android')
project(':capacitor-clipboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/clipboard/android')
include ':capacitor-device'
project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/device/android')
project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/device/android')
include ':capacitor-dialog'
project(':capacitor-dialog').projectDir = new File('../../../node_modules/.pnpm/@capacitor+dialog@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/dialog/android')
project(':capacitor-dialog').projectDir = new File('../../../node_modules/.pnpm/@capacitor+dialog@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/dialog/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../../../node_modules/.pnpm/@capacitor+filesystem@5.1.4_@capacitor+core@5.5.0/node_modules/@capacitor/filesystem/android')
project(':capacitor-filesystem').projectDir = new File('../../../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/filesystem/android')
include ':capacitor-geolocation'
project(':capacitor-geolocation').projectDir = new File('../../../node_modules/.pnpm/@capacitor+geolocation@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/geolocation/android')
project(':capacitor-geolocation').projectDir = new File('../../../node_modules/.pnpm/@capacitor+geolocation@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/geolocation/android')
include ':capacitor-haptics'
project(':capacitor-haptics').projectDir = new File('../../../node_modules/.pnpm/@capacitor+haptics@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/haptics/android')
project(':capacitor-haptics').projectDir = new File('../../../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/haptics/android')
include ':capacitor-keyboard'
project(':capacitor-keyboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+keyboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/keyboard/android')
project(':capacitor-keyboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+keyboard@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/keyboard/android')
include ':capacitor-local-notifications'
project(':capacitor-local-notifications').projectDir = new File('../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/local-notifications/android')
project(':capacitor-local-notifications').projectDir = new File('../../../node_modules/.pnpm/@capacitor+local-notifications@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/local-notifications/android')
include ':capacitor-network'
project(':capacitor-network').projectDir = new File('../../../node_modules/.pnpm/@capacitor+network@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/network/android')
project(':capacitor-network').projectDir = new File('../../../node_modules/.pnpm/@capacitor+network@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/network/android')
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/preferences/android')
project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/preferences/android')
include ':capacitor-screen-orientation'
project(':capacitor-screen-orientation').projectDir = new File('../../../node_modules/.pnpm/@capacitor+screen-orientation@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/screen-orientation/android')
include ':capacitor-share'
project(':capacitor-share').projectDir = new File('../../../node_modules/.pnpm/@capacitor+share@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/share/android')
project(':capacitor-share').projectDir = new File('../../../node_modules/.pnpm/@capacitor+share@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/share/android')
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@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/splash-screen/android')
include ':transistorsoft-capacitor-background-fetch'
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')
project(':transistorsoft-capacitor-background-fetch').projectDir = new File('../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.2.0_@capacitor+core@6.1.1/node_modules/@transistorsoft/capacitor-background-fetch/android')
include ':capacitor-secure-storage-plugin'
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')
project(':capacitor-secure-storage-plugin').projectDir = new File('../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.1.1/node_modules/capacitor-secure-storage-plugin/android')

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,14 +1,14 @@
ext {
minSdkVersion = 22
compileSdkVersion = 33
targetSdkVersion = 33
androidxActivityVersion = '1.7.0'
compileSdkVersion = 34
targetSdkVersion = 34
androidxActivityVersion = '1.8.0'
androidxAppCompatVersion = '1.6.1'
androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.10.0'
androidxFragmentVersion = '1.5.6'
coreSplashScreenVersion = '1.0.0'
androidxWebkitVersion = '1.6.1'
androidxCoreVersion = '1.12.0'
androidxFragmentVersion = '1.6.2'
coreSplashScreenVersion = '1.0.1'
androidxWebkitVersion = '1.9.0'
junitVersion = '4.13.2'
androidxJunitVersion = '1.1.5'
androidxEspressoCoreVersion = '3.5.1'

View File

@@ -1,4 +1,4 @@
require_relative '../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios/scripts/pods_helpers'
require_relative '../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios/scripts/pods_helpers'
platform :ios, '13.0'
use_frameworks!
@@ -9,24 +9,26 @@ use_frameworks!
install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods
pod 'Capacitor', :path => '../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios'
pod 'CapacitorApp', :path => '../../../../node_modules/.pnpm/@capacitor+app@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/app'
pod 'CapacitorBrowser', :path => '../../../../node_modules/.pnpm/@capacitor+browser@5.1.0_@capacitor+core@5.5.0/node_modules/@capacitor/browser'
pod 'CapacitorClipboard', :path => '../../../../node_modules/.pnpm/@capacitor+clipboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/clipboard'
pod 'CapacitorDevice', :path => '../../../../node_modules/.pnpm/@capacitor+device@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/device'
pod 'CapacitorDialog', :path => '../../../../node_modules/.pnpm/@capacitor+dialog@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/dialog'
pod 'CapacitorFilesystem', :path => '../../../../node_modules/.pnpm/@capacitor+filesystem@5.1.4_@capacitor+core@5.5.0/node_modules/@capacitor/filesystem'
pod 'CapacitorGeolocation', :path => '../../../../node_modules/.pnpm/@capacitor+geolocation@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/geolocation'
pod 'CapacitorHaptics', :path => '../../../../node_modules/.pnpm/@capacitor+haptics@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/haptics'
pod 'CapacitorKeyboard', :path => '../../../../node_modules/.pnpm/@capacitor+keyboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/keyboard'
pod 'CapacitorLocalNotifications', :path => '../../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/local-notifications'
pod 'CapacitorNetwork', :path => '../../../../node_modules/.pnpm/@capacitor+network@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/network'
pod 'CapacitorPreferences', :path => '../../../../node_modules/.pnpm/@capacitor+preferences@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/preferences'
pod 'CapacitorShare', :path => '../../../../node_modules/.pnpm/@capacitor+share@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/share'
pod 'CapacitorSplashScreen', :path => '../../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/splash-screen'
pod 'TransistorsoftCapacitorBackgroundFetch', :path => '../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.1.1_@capacitor+core@5.5.0/node_modules/@transistorsoft/capacitor-background-fetch'
pod 'CapacitorSecureStoragePlugin', :path => '../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.5.0/node_modules/capacitor-secure-storage-plugin'
pod 'Capacitor', :path => '../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios'
pod 'CapacitorCommunityScreenBrightness', :path => '../../../../node_modules/.pnpm/@capacitor-community+screen-brightness@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor-community/screen-brightness'
pod 'CapacitorApp', :path => '../../../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/app'
pod 'CapacitorBrowser', :path => '../../../../node_modules/.pnpm/@capacitor+browser@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/browser'
pod 'CapacitorClipboard', :path => '../../../../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/clipboard'
pod 'CapacitorDevice', :path => '../../../../node_modules/.pnpm/@capacitor+device@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/device'
pod 'CapacitorDialog', :path => '../../../../node_modules/.pnpm/@capacitor+dialog@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/dialog'
pod 'CapacitorFilesystem', :path => '../../../../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/filesystem'
pod 'CapacitorGeolocation', :path => '../../../../node_modules/.pnpm/@capacitor+geolocation@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/geolocation'
pod 'CapacitorHaptics', :path => '../../../../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/haptics'
pod 'CapacitorKeyboard', :path => '../../../../node_modules/.pnpm/@capacitor+keyboard@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/keyboard'
pod 'CapacitorLocalNotifications', :path => '../../../../node_modules/.pnpm/@capacitor+local-notifications@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/local-notifications'
pod 'CapacitorNetwork', :path => '../../../../node_modules/.pnpm/@capacitor+network@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/network'
pod 'CapacitorPreferences', :path => '../../../../node_modules/.pnpm/@capacitor+preferences@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/preferences'
pod 'CapacitorScreenOrientation', :path => '../../../../node_modules/.pnpm/@capacitor+screen-orientation@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/screen-orientation'
pod 'CapacitorShare', :path => '../../../../node_modules/.pnpm/@capacitor+share@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/share'
pod 'CapacitorSplashScreen', :path => '../../../../node_modules/.pnpm/@capacitor+splash-screen@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/splash-screen'
pod 'TransistorsoftCapacitorBackgroundFetch', :path => '../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.2.0_@capacitor+core@6.1.1/node_modules/@transistorsoft/capacitor-background-fetch'
pod 'CapacitorSecureStoragePlugin', :path => '../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.1.1/node_modules/capacitor-secure-storage-plugin'
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
end

View File

@@ -1,64 +1,70 @@
PODS:
- Capacitor (5.5.0):
- Capacitor (6.1.1):
- CapacitorCordova
- CapacitorApp (5.0.6):
- CapacitorApp (6.0.0):
- Capacitor
- CapacitorBrowser (5.1.0):
- CapacitorBrowser (6.0.1):
- Capacitor
- CapacitorClipboard (5.0.6):
- CapacitorClipboard (6.0.0):
- Capacitor
- CapacitorCordova (5.5.0)
- CapacitorDevice (5.0.6):
- CapacitorCommunityScreenBrightness (6.0.0):
- Capacitor
- CapacitorDialog (5.0.6):
- CapacitorCordova (6.1.1)
- CapacitorDevice (6.0.0):
- Capacitor
- CapacitorFilesystem (5.1.4):
- CapacitorDialog (6.0.0):
- Capacitor
- CapacitorGeolocation (5.0.6):
- CapacitorFilesystem (6.0.0):
- Capacitor
- CapacitorHaptics (5.0.6):
- CapacitorGeolocation (6.0.0):
- Capacitor
- CapacitorKeyboard (5.0.6):
- CapacitorHaptics (6.0.0):
- Capacitor
- CapacitorLocalNotifications (5.0.6):
- CapacitorKeyboard (6.0.1):
- Capacitor
- CapacitorNetwork (5.0.6):
- CapacitorLocalNotifications (6.0.0):
- Capacitor
- CapacitorPreferences (5.0.6):
- CapacitorNetwork (6.0.1):
- Capacitor
- CapacitorPreferences (6.0.1):
- Capacitor
- CapacitorScreenOrientation (6.0.1):
- Capacitor
- CapacitorSecureStoragePlugin (0.9.0):
- Capacitor
- SwiftKeychainWrapper
- CapacitorShare (5.0.6):
- CapacitorShare (6.0.1):
- Capacitor
- CapacitorSplashScreen (5.0.6):
- CapacitorSplashScreen (6.0.1):
- Capacitor
- CordovaPlugins (5.5.0):
- CordovaPlugins (6.1.1):
- CapacitorCordova
- SwiftKeychainWrapper (4.0.1)
- TransistorsoftCapacitorBackgroundFetch (5.1.1):
- TransistorsoftCapacitorBackgroundFetch (5.2.0):
- Capacitor
DEPENDENCIES:
- "Capacitor (from `../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios`)"
- "CapacitorApp (from `../../../../node_modules/.pnpm/@capacitor+app@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/app`)"
- "CapacitorBrowser (from `../../../../node_modules/.pnpm/@capacitor+browser@5.1.0_@capacitor+core@5.5.0/node_modules/@capacitor/browser`)"
- "CapacitorClipboard (from `../../../../node_modules/.pnpm/@capacitor+clipboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/clipboard`)"
- "CapacitorCordova (from `../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios`)"
- "CapacitorDevice (from `../../../../node_modules/.pnpm/@capacitor+device@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/device`)"
- "CapacitorDialog (from `../../../../node_modules/.pnpm/@capacitor+dialog@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/dialog`)"
- "CapacitorFilesystem (from `../../../../node_modules/.pnpm/@capacitor+filesystem@5.1.4_@capacitor+core@5.5.0/node_modules/@capacitor/filesystem`)"
- "CapacitorGeolocation (from `../../../../node_modules/.pnpm/@capacitor+geolocation@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/geolocation`)"
- "CapacitorHaptics (from `../../../../node_modules/.pnpm/@capacitor+haptics@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/haptics`)"
- "CapacitorKeyboard (from `../../../../node_modules/.pnpm/@capacitor+keyboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/keyboard`)"
- "CapacitorLocalNotifications (from `../../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/local-notifications`)"
- "CapacitorNetwork (from `../../../../node_modules/.pnpm/@capacitor+network@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/network`)"
- "CapacitorPreferences (from `../../../../node_modules/.pnpm/@capacitor+preferences@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/preferences`)"
- "CapacitorSecureStoragePlugin (from `../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.5.0/node_modules/capacitor-secure-storage-plugin`)"
- "CapacitorShare (from `../../../../node_modules/.pnpm/@capacitor+share@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/share`)"
- "CapacitorSplashScreen (from `../../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/splash-screen`)"
- "Capacitor (from `../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios`)"
- "CapacitorApp (from `../../../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/app`)"
- "CapacitorBrowser (from `../../../../node_modules/.pnpm/@capacitor+browser@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/browser`)"
- "CapacitorClipboard (from `../../../../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/clipboard`)"
- "CapacitorCommunityScreenBrightness (from `../../../../node_modules/.pnpm/@capacitor-community+screen-brightness@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor-community/screen-brightness`)"
- "CapacitorCordova (from `../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios`)"
- "CapacitorDevice (from `../../../../node_modules/.pnpm/@capacitor+device@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/device`)"
- "CapacitorDialog (from `../../../../node_modules/.pnpm/@capacitor+dialog@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/dialog`)"
- "CapacitorFilesystem (from `../../../../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/filesystem`)"
- "CapacitorGeolocation (from `../../../../node_modules/.pnpm/@capacitor+geolocation@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/geolocation`)"
- "CapacitorHaptics (from `../../../../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/haptics`)"
- "CapacitorKeyboard (from `../../../../node_modules/.pnpm/@capacitor+keyboard@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/keyboard`)"
- "CapacitorLocalNotifications (from `../../../../node_modules/.pnpm/@capacitor+local-notifications@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/local-notifications`)"
- "CapacitorNetwork (from `../../../../node_modules/.pnpm/@capacitor+network@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/network`)"
- "CapacitorPreferences (from `../../../../node_modules/.pnpm/@capacitor+preferences@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/preferences`)"
- "CapacitorScreenOrientation (from `../../../../node_modules/.pnpm/@capacitor+screen-orientation@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/screen-orientation`)"
- "CapacitorSecureStoragePlugin (from `../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.1.1/node_modules/capacitor-secure-storage-plugin`)"
- "CapacitorShare (from `../../../../node_modules/.pnpm/@capacitor+share@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/share`)"
- "CapacitorSplashScreen (from `../../../../node_modules/.pnpm/@capacitor+splash-screen@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/splash-screen`)"
- CordovaPlugins (from `../capacitor-cordova-ios-plugins`)
- "TransistorsoftCapacitorBackgroundFetch (from `../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.1.1_@capacitor+core@5.5.0/node_modules/@transistorsoft/capacitor-background-fetch`)"
- "TransistorsoftCapacitorBackgroundFetch (from `../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.2.0_@capacitor+core@6.1.1/node_modules/@transistorsoft/capacitor-background-fetch`)"
SPEC REPOS:
trunk:
@@ -66,66 +72,72 @@ SPEC REPOS:
EXTERNAL SOURCES:
Capacitor:
:path: "../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios"
:path: "../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios"
CapacitorApp:
:path: "../../../../node_modules/.pnpm/@capacitor+app@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/app"
:path: "../../../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/app"
CapacitorBrowser:
:path: "../../../../node_modules/.pnpm/@capacitor+browser@5.1.0_@capacitor+core@5.5.0/node_modules/@capacitor/browser"
:path: "../../../../node_modules/.pnpm/@capacitor+browser@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/browser"
CapacitorClipboard:
:path: "../../../../node_modules/.pnpm/@capacitor+clipboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/clipboard"
:path: "../../../../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/clipboard"
CapacitorCommunityScreenBrightness:
:path: "../../../../node_modules/.pnpm/@capacitor-community+screen-brightness@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor-community/screen-brightness"
CapacitorCordova:
:path: "../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios"
:path: "../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios"
CapacitorDevice:
:path: "../../../../node_modules/.pnpm/@capacitor+device@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/device"
:path: "../../../../node_modules/.pnpm/@capacitor+device@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/device"
CapacitorDialog:
:path: "../../../../node_modules/.pnpm/@capacitor+dialog@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/dialog"
:path: "../../../../node_modules/.pnpm/@capacitor+dialog@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/dialog"
CapacitorFilesystem:
:path: "../../../../node_modules/.pnpm/@capacitor+filesystem@5.1.4_@capacitor+core@5.5.0/node_modules/@capacitor/filesystem"
:path: "../../../../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/filesystem"
CapacitorGeolocation:
:path: "../../../../node_modules/.pnpm/@capacitor+geolocation@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/geolocation"
:path: "../../../../node_modules/.pnpm/@capacitor+geolocation@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/geolocation"
CapacitorHaptics:
:path: "../../../../node_modules/.pnpm/@capacitor+haptics@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/haptics"
:path: "../../../../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/haptics"
CapacitorKeyboard:
:path: "../../../../node_modules/.pnpm/@capacitor+keyboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/keyboard"
:path: "../../../../node_modules/.pnpm/@capacitor+keyboard@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/keyboard"
CapacitorLocalNotifications:
:path: "../../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/local-notifications"
:path: "../../../../node_modules/.pnpm/@capacitor+local-notifications@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/local-notifications"
CapacitorNetwork:
:path: "../../../../node_modules/.pnpm/@capacitor+network@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/network"
:path: "../../../../node_modules/.pnpm/@capacitor+network@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/network"
CapacitorPreferences:
:path: "../../../../node_modules/.pnpm/@capacitor+preferences@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/preferences"
:path: "../../../../node_modules/.pnpm/@capacitor+preferences@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/preferences"
CapacitorScreenOrientation:
:path: "../../../../node_modules/.pnpm/@capacitor+screen-orientation@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/screen-orientation"
CapacitorSecureStoragePlugin:
:path: "../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.5.0/node_modules/capacitor-secure-storage-plugin"
:path: "../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.1.1/node_modules/capacitor-secure-storage-plugin"
CapacitorShare:
:path: "../../../../node_modules/.pnpm/@capacitor+share@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/share"
:path: "../../../../node_modules/.pnpm/@capacitor+share@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/share"
CapacitorSplashScreen:
:path: "../../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/splash-screen"
:path: "../../../../node_modules/.pnpm/@capacitor+splash-screen@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/splash-screen"
CordovaPlugins:
:path: "../capacitor-cordova-ios-plugins"
TransistorsoftCapacitorBackgroundFetch:
:path: "../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.1.1_@capacitor+core@5.5.0/node_modules/@transistorsoft/capacitor-background-fetch"
:path: "../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.2.0_@capacitor+core@6.1.1/node_modules/@transistorsoft/capacitor-background-fetch"
SPEC CHECKSUMS:
Capacitor: 57890b363df14d5d2d5d8461aa23e886cb34da2a
CapacitorApp: 024e1b1bea5f883d79f6330d309bc441c88ad04a
CapacitorBrowser: 7a0fb6a1011abfaaf2dfedfd8248f942a8eda3d6
CapacitorClipboard: 77edf49827ea21da2a9c05c690a4a6a4d07199c4
CapacitorCordova: 3d3908a3d208a11a75f9df3b18c4405c4de76e1d
CapacitorDevice: 2c968f98a1ec4d22357418c1521e7ddc46c675e6
CapacitorDialog: 0f3c15dfe9414b83bc64aef4078f1b92bcfead26
CapacitorFilesystem: af704badfbc69f6f8623d9ed313e5490e3723dcb
CapacitorGeolocation: 7be5f77abc205c0efe319fff8587a7183e7b0240
CapacitorHaptics: 1fffc1217c7e64a472d7845be50fb0c2f7d4204c
CapacitorKeyboard: b978154b024a5f65e044908e37d15b7de58b9d12
CapacitorLocalNotifications: c2d8b14794064fd4814b1d6c4ddbac8029afa295
CapacitorNetwork: d80b3e79bef6ec37640ee2806c19771f07ff2d0c
CapacitorPreferences: f03954bcb0ff09c792909e46bff88e3183c16b10
Capacitor: 8941aba4364ba9d1b22188569001f2ce45cc2b00
CapacitorApp: 9d53aec7101f7b030a950c5bdc4df8612576b279
CapacitorBrowser: 473c7fd70ddbe541608ff09ec1be14da0078279e
CapacitorClipboard: 80282f684154124b9019ebf401235b70b0cf4994
CapacitorCommunityScreenBrightness: 250184917bd0738a681c026a9513e18ddb0cb49b
CapacitorCordova: 8f2cc8d8d3619c566e9418fe8772064a94266106
CapacitorDevice: f8fd88f9edd1261c55a109f32015b09bbbfdc4a0
CapacitorDialog: 64aa82840ee5e9c066f9e1a49c1e186afd3f24d2
CapacitorFilesystem: 60e59ba274c234a979e7a3be2552feaadcee4263
CapacitorGeolocation: 1f12bbe372b65116e851bd8e3a94cf0ef9bacebb
CapacitorHaptics: 9ebc9363f0e9b8eb4295088a0b474530acf1859b
CapacitorKeyboard: 5f32a712adf41e07a61caafb82cf29fb6d8ba123
CapacitorLocalNotifications: 4ab68f0be5f697a579558fadd307d823a9ec1c26
CapacitorNetwork: 5c94acfdddc22043f2ffaff224ce9b4aa5a179f0
CapacitorPreferences: 72909b165bc7807103778ddbb86d5d8ce06abf71
CapacitorScreenOrientation: 3d4965dcbda5d901b6a48350a5d270ad7d404e05
CapacitorSecureStoragePlugin: e91d7df060f2495a1acff9583641a6953e3aacba
CapacitorShare: cd41743331cb71d217c029de54b681cbd91e0fcc
CapacitorSplashScreen: 5fa2ab5e46cf5cc530cf16a51c80c7a986579ccd
CordovaPlugins: de5669381702d76ed5b1d442177a6a5fc3252a9d
CapacitorShare: 02222f2457ff003e642370a9c1ecd101baaa27c8
CapacitorSplashScreen: 61645214e8f955ff2b80f16a6a3648960fe4c89f
CordovaPlugins: 1078156cfc354dd440b38ce4062e69fd9b07033c
SwiftKeychainWrapper: 807ba1d63c33a7d0613288512399cd1eda1e470c
TransistorsoftCapacitorBackgroundFetch: ce4b3e01b898cef516e68485d2160a078016ee97
TransistorsoftCapacitorBackgroundFetch: f130c5dcb6048401e74cc531e691e1d513045366
PODFILE CHECKSUM: 229278f2c257e8ab555325c7115b2e187e8e628d
PODFILE CHECKSUM: d0561b89b66368df409c77587e3e02b48e737203
COCOAPODS: 1.13.0
COCOAPODS: 1.15.2

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/app",
"description": "The generic app tailored to fulfill needs of German universities, written using Ionic Framework.",
"version": "3.2.0",
"version": "3.3.2",
"private": true,
"license": "GPL-3.0-only",
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
@@ -61,21 +61,23 @@
"@angular/router": "17.3.0",
"@awesome-cordova-plugins/calendar": "6.6.0",
"@awesome-cordova-plugins/core": "6.6.0",
"@capacitor/app": "5.0.7",
"@capacitor/browser": "5.2.0",
"@capacitor/clipboard": "5.0.7",
"@capacitor/core": "5.7.3",
"@capacitor/device": "5.0.7",
"@capacitor/dialog": "5.0.7",
"@capacitor/filesystem": "5.2.1",
"@capacitor/geolocation": "5.0.7",
"@capacitor/haptics": "5.0.7",
"@capacitor/keyboard": "5.0.8",
"@capacitor/local-notifications": "5.0.7",
"@capacitor/network": "5.0.7",
"@capacitor/preferences": "5.0.7",
"@capacitor/share": "5.0.7",
"@capacitor/splash-screen": "5.0.7",
"@capacitor-community/screen-brightness": "6.0.0",
"@capacitor/app": "6.0.0",
"@capacitor/browser": "6.0.1",
"@capacitor/clipboard": "6.0.0",
"@capacitor/core": "6.1.1",
"@capacitor/device": "6.0.0",
"@capacitor/dialog": "6.0.0",
"@capacitor/filesystem": "6.0.0",
"@capacitor/geolocation": "6.0.0",
"@capacitor/haptics": "6.0.0",
"@capacitor/keyboard": "6.0.1",
"@capacitor/local-notifications": "6.0.0",
"@capacitor/network": "6.0.1",
"@capacitor/preferences": "6.0.1",
"@capacitor/screen-orientation": "6.0.1",
"@capacitor/share": "6.0.1",
"@capacitor/splash-screen": "6.0.1",
"@ionic-native/core": "5.36.0",
"@ionic/angular": "7.8.0",
"@ionic/storage-angular": "4.0.0",
@@ -127,10 +129,10 @@
"@angular/language-server": "17.3.0",
"@angular/language-service": "17.3.0",
"@angular/platform-browser-dynamic": "17.3.0",
"@capacitor/android": "5.7.3",
"@capacitor/android": "6.1.1",
"@capacitor/assets": "3.0.4",
"@capacitor/cli": "5.7.3",
"@capacitor/ios": "5.7.3",
"@capacitor/cli": "6.1.1",
"@capacitor/ios": "6.1.1",
"@compodoc/compodoc": "1.1.23",
"@cypress/schematic": "2.5.1",
"@ionic/angular-toolkit": "11.0.1",

View File

@@ -0,0 +1,8 @@
import {networkInterfaces} from 'os';
console.log(
Object.entries(networkInterfaces())
.map(([, info]) => info)
.flat()
.find(info => info && !info.internal && info.family === 'IPv4')?.address,
);

View File

@@ -12,24 +12,6 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
ion-content > div {
height: 100%;
}
cdk-virtual-scroll-viewport {
width: 100%;
height: 100%;
}
:host ::ng-deep {
.cdk-virtual-scroll-content-wrapper {
width: 100%;
}
}
.virtual-scroll-expander {
clear: both;
}
.supertext-icon {
height: 14px;

View File

@@ -24,13 +24,17 @@ import {
} from './errors';
import {NGXLogger} from 'ngx-logger';
import {sampleIndexResponse} from '../../_helpers/data/sample-configuration';
import {BehaviorSubject} from 'rxjs';
import {InternetConnectionService} from '../../util/internet-connection.service';
describe('ConfigProvider', () => {
let internetConnectionServiceMock: {offline$: BehaviorSubject<boolean>};
let configProvider: ConfigProvider;
let storageProviderSpy: jasmine.SpyObj<StorageProvider>;
let ngxLogger: jasmine.SpyObj<NGXLogger>;
beforeEach(() => {
internetConnectionServiceMock = {offline$: new BehaviorSubject<boolean>(false)};
storageProviderSpy = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put']);
const webHttpClientMethodSpy = jasmine.createSpyObj('StAppsWebHttpClient', ['request']);
ngxLogger = jasmine.createSpyObj('NGXLogger', ['log', 'error', 'warn']);
@@ -51,6 +55,10 @@ describe('ConfigProvider', () => {
provide: NGXLogger,
useValue: ngxLogger,
},
{
provide: InternetConnectionService,
useValue: internetConnectionServiceMock,
},
],
});
@@ -75,6 +83,22 @@ describe('ConfigProvider', () => {
expect(error).toEqual(new ConfigFetchError());
});
it('should throw device offline error when offline', async () => {
// eslint-disable-next-line unicorn/error-message
let error = new Error('');
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
internetConnectionServiceMock.offline$ = new BehaviorSubject<boolean>(true);
try {
await configProvider.fetch();
} catch (error_) {
error = error_ as Error;
expect(error).toBeInstanceOf(ConfigFetchError);
expect(error.message).toContain('Device is offline.');
}
});
it('should init from remote and saved config not available', async () => {
storageProviderSpy.has.and.returnValue(Promise.resolve(false));
spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(sampleIndexResponse));
@@ -108,6 +132,8 @@ describe('ConfigProvider', () => {
wrongConfig.backend.SCVersion = '0.1.0';
storageProviderSpy.get.and.returnValue(Promise.resolve(wrongConfig));
spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(sampleIndexResponse));
// sets StApps core version which has a different major version
configProvider.scVersion = '1.0.0';
await configProvider.init();
expect(ngxLogger.warn).toHaveBeenCalledWith(
@@ -115,6 +141,19 @@ describe('ConfigProvider', () => {
);
});
it('should load from storage if offline and same major version stored', async () => {
internetConnectionServiceMock = {offline$: new BehaviorSubject<boolean>(true)};
storageProviderSpy.has.and.returnValue(Promise.resolve(true));
const configInStorage = structuredClone(sampleIndexResponse);
configInStorage.backend.SCVersion = '1.0.0';
storageProviderSpy.get.and.returnValue(Promise.resolve(configInStorage));
// sets StApps core version which has the same major version
configProvider.scVersion = '1.1.1';
await configProvider.init();
expect(configProvider.getAnyValue('app')).toEqual(configInStorage.app);
});
it('should throw error on saved app configuration not available', async () => {
storageProviderSpy.has.and.returnValue(Promise.resolve(false));
// eslint-disable-next-line unicorn/error-message

View File

@@ -27,6 +27,8 @@ import {
SavedConfigNotAvailable,
WrongConfigVersionInStorage,
} from './errors';
import {InternetConnectionService} from '../../util/internet-connection.service';
import {firstValueFrom} from 'rxjs';
/**
* Key to store config in storage module
@@ -72,6 +74,7 @@ export class ConfigProvider {
private readonly storageProvider: StorageProvider,
swHttpClient: StAppsWebHttpClient,
private readonly logger: NGXLogger,
private readonly internetConnectionService: InternetConnectionService,
) {
console.log('config init');
this.client = new Client(swHttpClient, environment.backend_url, environment.backend_version);
@@ -82,9 +85,15 @@ export class ConfigProvider {
*/
async fetch(): Promise<SCIndexResponse> {
try {
return await this.client.handshake(this.scVersion);
} catch {
throw new ConfigFetchError();
const isOffline = await firstValueFrom(this.internetConnectionService.offline$);
if (isOffline) {
throw new Error('Device is offline.');
} else {
return await this.client.handshake(this.scVersion);
}
} catch (error) {
const error_ = error instanceof Error ? new ConfigFetchError(error.message) : new ConfigFetchError();
throw error_;
}
}
@@ -123,7 +132,7 @@ export class ConfigProvider {
this.config = await this.loadLocal();
this.firstSession = false;
this.logger.log(`initialised configuration from storage`);
if (this.config.backend.SCVersion !== this.scVersion) {
if (this.config.backend.SCVersion.split('.')[0] !== this.scVersion.split('.')[0]) {
loadError = new WrongConfigVersionInStorage(this.scVersion, this.config.backend.SCVersion);
}
} catch (error) {

View File

@@ -19,8 +19,10 @@ import {AppError} from '../../_helpers/errors';
* Error that is thrown when fetching from backend fails
*/
export class ConfigFetchError extends AppError {
constructor() {
super('ConfigFetchError', 'App configuration could not be fetched!');
constructor(reason?: string) {
const defaultMessage = 'App configuration could not be fetched!';
const message = reason ? `${defaultMessage} ${reason}` : defaultMessage;
super('ConfigFetchError', message);
}
}

View File

@@ -33,9 +33,9 @@ ion-item {
margin: var(--spacing-sm);
ion-thumbnail {
--ion-margin: var(--spacing-xs);
--size: 36px;
margin-block: auto;
margin: 0;
margin-inline: var(--spacing-md);
padding: 0;
}

View File

@@ -24,6 +24,7 @@ import {
SCSearchQuery,
SCSearchSort,
SCThings,
SCSearchSuggestions,
} from '@openstapps/core';
import {NGXLogger} from 'ngx-logger';
import {combineLatest, Subject} from 'rxjs';
@@ -110,6 +111,8 @@ export class SearchPageComponent implements OnInit {
*/
items: Promise<SCThings[]>;
suggestions: SCSearchSuggestions | undefined;
/**
* Page size of queries
*/
@@ -219,6 +222,7 @@ export class SearchPageComponent implements OnInit {
try {
const result = await this.dataProvider.search(searchOptions);
this.suggestions = result.suggestions;
this.singleTypeResponse = result.facets.find(facet => facet.field === 'type')?.buckets.length === 1;
if (append) {
// append results
@@ -283,6 +287,12 @@ export class SearchPageComponent implements OnInit {
this.contextMenuService.updateContextFilter(facets);
}
applySuggestion(target: string, suggestion: string) {
this.queryText = this.queryText.replaceAll(target, suggestion);
this.suggestions = undefined;
this.searchStringChanged(this.queryText);
}
ngOnInit(defaultListeners = true) {
this.initialize();
this.contextMenuService.setContextSort({

View File

@@ -69,7 +69,23 @@
</ion-header>
<ion-content class="content">
<div class="suggestions">
@if (suggestions?.terms; as terms) {
<span>{{ 'search.SUGGESTIONS' | translate }}: </span>
@for (suggestion of terms | keyvalue; track suggestion) {
@for (term of suggestion.value; track term) {
@if ($index == 0) {
<b (click)="applySuggestion(suggestion.key, term)" class="suggestion">{{ term }}</b>
} @else {
<span (click)="applySuggestion(suggestion.key, term)" class="suggestion">{{ term }}</span>
}
}
}
}
</div>
<div
class="hint"
[class.no-results]="!showDefaultData && !items && !loading"
[style.display]="!showDefaultData && !items && !loading ? 'block' : 'none'"
>

View File

@@ -46,7 +46,7 @@ ion-content {
--background: var(--ion-background-color);
}
.content > div {
.content > .hint {
height: 100%;
ion-label.centered-message-container {
@@ -60,3 +60,19 @@ ion-content {
ion-header {
background: var(--ion-color-primary);
}
.suggestions {
padding: var(--spacing-md);
padding-block-end: 0;
}
.suggestion {
cursor: pointer;
}
.suggestion + .suggestion::before {
cursor: text;
content: ',';
padding-inline-end: 0.25ch;
font-weight: normal;
}

View File

@@ -11,7 +11,7 @@ export class LibraryAccountPageComponent {
constructor(private readonly libraryAccountService: LibraryAccountService) {}
async ionViewWillEnter(): Promise<void> {
const patron = await this.libraryAccountService.getProfile();
const patron = await this.libraryAccountService.getPatron();
this.name = patron?.name;
}
}

View File

@@ -39,6 +39,8 @@ export class CheckedOutPageComponent {
async fetchItems() {
try {
// Prepare patron (status) for the items
await this.libraryAccountService.getPatron();
this.checkedOutItems = undefined;
this.checkedOutItems = await this.libraryAccountService.getFilteredItems([PAIADocumentStatus.Held]);
} catch {

View File

@@ -27,7 +27,7 @@
@for (checkedOutItem of checkedOutItems; track checkedOutItem) {
<stapps-paia-item
[item]="checkedOutItem"
[propertiesToShow]="['label', 'renewals', 'endtime']"
[propertiesToShow]="['label', 'renewals', 'duedate']"
(documentAction)="onDocumentAction($event)"
listName="checked_out"
>

View File

@@ -14,7 +14,8 @@
*/
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {DocumentAction, PAIADocument} from '../../../types';
import {DocumentAction, PAIADocument, PAIADocumentStatus, PAIADocumentVisualStatus} from '../../../types';
import {LibraryAccountService} from '../../library-account.service';
@Component({
selector: 'stapps-paia-item',
@@ -22,7 +23,24 @@ import {DocumentAction, PAIADocument} from '../../../types';
styleUrls: ['./paiaitem.scss'],
})
export class PAIAItemComponent {
@Input() item: PAIADocument;
private _item: PAIADocument;
renewable: boolean;
visualStatus?: PAIADocumentVisualStatus;
constructor(private readonly libraryAccountService: LibraryAccountService) {}
@Input()
set item(value: PAIADocument) {
this._item = value;
void this.setRenewable();
this.visualStatus = this.getVisualStatus(Number(this.item.status));
}
get item(): PAIADocument {
return this._item;
}
@Input()
propertiesToShow: (keyof PAIADocument)[];
@@ -36,4 +54,23 @@ export class PAIAItemComponent {
async onClick(action: DocumentAction['action']) {
this.documentAction.emit({doc: this.item, action});
}
private async setRenewable() {
const isActive = await this.libraryAccountService.isActivePatron();
this.renewable = isActive && Number(this.item.status) === PAIADocumentStatus.Held;
}
private getVisualStatus(status: PAIADocumentStatus): PAIADocumentVisualStatus | undefined {
switch (status) {
case PAIADocumentStatus.Ordered: {
return {color: 'warning', status: status, statusText: 'ordered'};
}
case PAIADocumentStatus.Provided: {
return {color: 'success', status: status, statusText: 'ready'};
}
default: {
return undefined;
}
}
}
}

View File

@@ -14,16 +14,22 @@
-->
<ion-item>
<!-- TODO: text not selectable in Chrome, bugfix needed https://github.com/ionic-team/ionic-framework/issues/24956 -->
<ion-label class="ion-text-wrap">
@if (item.about) {
<h2 class="name">{{ item.about }}</h2>
<h2 class="name">
@if (visualStatus) {
<ion-badge [color]="visualStatus.color" slot="start">
{{ 'library.account.pages' + '.' + listName + '.' + visualStatus.statusText | translate }}
</ion-badge>
}
{{ item.about }}
</h2>
}
@for (property of propertiesToShow; track property) {
@if (item[property]) {
<p>
{{ 'library.account.pages' + '.' + listName + '.' + 'labels' + '.' + property | translate }}:
@if (!['endtime', 'duedate'].includes(property)) {
@if (!['starttime', 'duedate'].includes(property)) {
{{ item[property] }}
} @else {
{{ $any(item[property]) | amDateFormat: 'll' }}
@@ -40,7 +46,7 @@
<!-- >-->
<!-- {{ 'library.account.actions.cancel.header' | translate }}</ion-button-->
<!-- >-->
@if (item.canrenew) {
@if (renewable && item.canrenew) {
<ion-button color="primary" (click)="onClick('renew')">
{{ 'library.account.actions.renew.header' | translate }}</ion-button
>

View File

@@ -12,3 +12,6 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
ion-badge {
vertical-align: bottom;
}

View File

@@ -45,6 +45,8 @@ export class HoldsPageComponent {
? [PAIADocumentStatus.Reserved]
: [PAIADocumentStatus.Ordered, PAIADocumentStatus.Provided];
try {
// Prepare patron (status) for the items
await this.libraryAccountService.getPatron();
this.paiaDocuments = await this.libraryAccountService.getFilteredItems(itemsStatus);
} catch {
await this.libraryAccountService.handleError();

View File

@@ -35,30 +35,20 @@
@switch (activeSegment) {
@case ('orders') {
@for (hold of paiaDocuments; track hold) {
@if (toNumber(hold.status) === paiaDocumentStatus.Provided) {
<stapps-paia-item
[item]="hold"
[propertiesToShow]="['label', 'storage']"
(documentAction)="onDocumentAction($event)"
listName="holds"
>
</stapps-paia-item>
} @else {
<stapps-paia-item
[item]="hold"
[propertiesToShow]="['label']"
(documentAction)="onDocumentAction($event)"
listName="holds"
>
</stapps-paia-item>
}
<stapps-paia-item
[item]="hold"
[propertiesToShow]="['label', 'storage']"
(documentAction)="onDocumentAction($event)"
listName="holds"
>
</stapps-paia-item>
}
}
@case ('reservations') {
@for (hold of paiaDocuments; track hold) {
<stapps-paia-item
[item]="hold"
[propertiesToShow]="['label']"
[propertiesToShow]="['label', 'starttime', 'storage', 'queue']"
(documentAction)="onDocumentAction($event)"
listName="holds"
>

View File

@@ -20,7 +20,15 @@ import {
SCFeatureConfiguration,
SCFeatureConfigurationExtern,
} from '@openstapps/core';
import {DocumentAction, PAIADocument, PAIADocumentStatus, PAIAFees, PAIAItems, PAIAPatron} from '../types';
import {
DocumentAction,
PAIADocument,
PAIADocumentStatus,
PAIAFees,
PAIAItems,
PAIAPatron,
PAIAPatronStatus,
} from '../types';
import {HebisDataProvider} from '../../hebis/hebis-data.provider';
import {PAIATokenResponse} from '../../auth/paia/paia-token-response';
import {AuthHelperService} from '../../auth/auth-helper.service';
@@ -43,6 +51,11 @@ export class LibraryAccountService {
*/
authType: SCAuthorizationProviderType;
/**
* Account (Patron) status
*/
private status?: PAIAPatronStatus;
constructor(
protected requestor: Requestor = new JQueryRequestor(),
private readonly hebisDataProvider: HebisDataProvider,
@@ -60,12 +73,23 @@ export class LibraryAccountService {
this.authType = config.authProvider as SCAuthorizationProviderType;
}
async getProfile() {
const patron = ((await this.getValidToken()) as PAIATokenResponse).patron;
return {
async getPatron() {
const patronId = ((await this.getValidToken()) as PAIATokenResponse).patron;
const patron = {
...(await this.performRequest<PAIAPatron>(`${this.baseUrl}/{patron}`)),
id: patron,
id: patronId,
} as PAIAPatron;
// Refresh the status
this.status = Number(patron.status);
return patron;
}
async getPatronStatus() {
return this.status ?? (this.status = Number((await this.getPatron()).status) as PAIAPatronStatus);
}
async isActivePatron() {
return (await this.getPatronStatus()) === PAIAPatronStatus.Active;
}
async getItems() {

View File

@@ -25,15 +25,17 @@ import {PAIAPatron} from '../../types';
export class ProfilePageComponent {
patron?: PAIAPatron;
propertiesToShow: (keyof PAIAPatron)[] = ['id', 'name', 'email', 'address', 'expires', 'note'];
constructor(private readonly libraryAccountService: LibraryAccountService) {}
async ionViewWillEnter(): Promise<void> {
try {
this.patron = await this.libraryAccountService.getProfile();
this.patron = await this.libraryAccountService.getPatron();
} catch {
await this.libraryAccountService.handleError();
}
}
isUnlimitedExpiry(date = ''): boolean {
return new Date(date).getFullYear() === 9999;
}
}

View File

@@ -26,27 +26,45 @@
<ion-card>
<ion-card-content>
<ion-grid>
@for (property of propertiesToShow; track property) {
@if (patron[property]) {
<ion-row>
<ion-col>
{{ 'library.account.pages.profile.labels' + '.' + property | translate }}:
</ion-col>
<ion-col>
@if (!['expires'].includes(property)) {
{{ patron[property] }}
} @else {
@if (patron[property] === '9999-12-31') {
{{ 'library.account.pages.profile.values.unlimited' | translate }}
} @else {
{{ 'library.account.pages.profile.values.expires' | translate }}:&nbsp;{{
patron[property] | amDateFormat: 'll'
}}
}
}
</ion-col>
</ion-row>
}
<ion-row>
<ion-col> {{ 'library.account.pages.profile.labels.id' | translate }}: </ion-col>
<ion-col>
{{ patron.id }}
</ion-col>
</ion-row>
<ion-row>
<ion-col> {{ 'library.account.pages.profile.labels.name' | translate }}: </ion-col>
<ion-col> {{ patron.name_details?.title ?? '' }} {{ patron.name }} </ion-col>
</ion-row>
@if (patron.email) {
<ion-row>
<ion-col> {{ 'library.account.pages.profile.labels.email' | translate }}: </ion-col>
<ion-col>
{{ patron.email }}
</ion-col>
</ion-row>
}
@if (patron.address) {
<ion-row>
<ion-col> {{ 'library.account.pages.profile.labels.address' | translate }}: </ion-col>
<ion-col>
{{ patron.address }}
</ion-col>
</ion-row>
}
@if (patron.expires) {
<ion-row>
<ion-col> {{ 'library.account.pages.profile.labels.expires' | translate }}: </ion-col>
<ion-col>
@if (isUnlimitedExpiry(patron.expires)) {
{{ 'library.account.pages.profile.values.unlimited' | translate }}
} @else {
{{ 'library.account.pages.profile.values.expires' | translate }}:&nbsp;{{
patron.expires | amDateFormat: 'll'
}}
}
</ion-col>
</ion-row>
}
</ion-grid>
</ion-card-content>

View File

@@ -19,9 +19,24 @@ export interface PAIAPatron {
email?: string;
address?: string;
expires?: string;
status?: string;
status?: PAIAPatronStatus;
type?: string;
note?: string;
// HeBIS specific property (not in PAIA documentation)
name_details?: {
firstname?: string;
lastname?: string;
gender?: string;
title?: string;
};
}
export enum PAIAPatronStatus {
Active = 0,
Inactive = 1,
InactiveExpired = 2,
InactiveOutstandingFees = 3,
InactiveExpiredOutstandingFees = 4,
}
/*
@@ -39,7 +54,7 @@ export interface PAIADocument {
queue?: string;
renewals?: string;
reminder?: string;
endtime?: string;
starttime?: string;
duedate?: string;
cancancel?: boolean;
canrenew?: boolean;
@@ -80,3 +95,9 @@ export interface DocumentAction {
action: 'cancel' | 'renew';
doc: PAIADocument;
}
export interface PAIADocumentVisualStatus {
color: 'warning' | 'success';
status: PAIADocumentStatus;
statusText: 'ordered' | 'ready';
}

View File

@@ -1,5 +1,6 @@
import {Directive, Input, Host} from '@angular/core';
import {MapComponent} from '@maplibre/ngx-maplibre-gl';
import {environment} from '../../../environments/environment';
@Directive({
selector: 'mgl-map[styleName]',
@@ -10,7 +11,7 @@ export class MapStyleDirective {
@Input()
set styleName(name: string) {
const style = `https://maps.server.uni-frankfurt.de/static/styles/${name}/style.json`;
const style = `${environment.backend_url}/_static/map/styles/${name}/style.json`;
if (this.map.style) {
this.map.mapInstance.setStyle(style);
} else {

View File

@@ -1,10 +1,14 @@
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
import {ChangeDetectionStrategy, Component, ElementRef, Input} from '@angular/core';
import {SCIdCard} from '@openstapps/core';
import {FullScreenImageDirective} from '../../util/full-screen-image.directive';
import {ThingTranslateModule} from '../../translation/thing-translate.module';
import {AsyncPipe, TitleCasePipe} from '@angular/common';
import {InRangeNowPipe, ToDateRangePipe} from '../../util/in-range.pipe';
import {IntervalIsNowPipe, ToDateIntervalPipe} from '../../util/in-range.pipe';
import {TranslateModule} from '@ngx-translate/core';
import {AnimationController, ModalController} from '@ionic/angular';
import {ScreenBrightness} from '@capacitor-community/screen-brightness';
import {ScreenOrientation} from '@capacitor/screen-orientation';
import {Capacitor} from '@capacitor/core';
import {iosDuration, iosEasing, mdDuration, mdEasing} from 'src/app/animation/easings';
@Component({
selector: 'stapps-id-card',
@@ -12,12 +16,10 @@ import {TranslateModule} from '@ngx-translate/core';
styleUrls: ['id-card.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
hostDirectives: [FullScreenImageDirective],
imports: [
FullScreenImageDirective,
ThingTranslateModule,
InRangeNowPipe,
ToDateRangePipe,
IntervalIsNowPipe,
ToDateIntervalPipe,
AsyncPipe,
TranslateModule,
TitleCasePipe,
@@ -25,4 +27,126 @@ import {TranslateModule} from '@ngx-translate/core';
})
export class IdCardComponent {
@Input({required: true}) item: SCIdCard;
constructor(
private modalController: ModalController,
private animationController: AnimationController,
private elementRef: ElementRef,
) {}
async presentFullscreen() {
const top = await this.modalController.getTop();
const mode = document.querySelector('ion-app')!.getAttribute('mode');
if (top) return;
if (window.innerWidth >= 768) return;
if (Capacitor.isNativePlatform()) {
const orientation = await ScreenOrientation.orientation();
ScreenOrientation.lock({orientation: 'portrait'});
if (orientation.type.startsWith('landscape')) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
const img: HTMLImageElement = this.elementRef.nativeElement.querySelector('img')!;
const safeArea = (location: string) =>
Number(img.computedStyleMap().get(`--ion-safe-area-${location}`)!.toString().replace(/px$/, ''));
const safeAreaTop = safeArea('top');
const safeAreaBottom = safeArea('bottom');
const safeAreaLeft = safeArea('left');
const safeAreaRight = safeArea('right');
const windowWidth = window.innerWidth - safeAreaLeft - safeAreaRight;
const windowHeight = window.innerHeight - safeAreaTop - safeAreaBottom;
const isLandscape = windowWidth > windowHeight;
const nativeBounds = img.getBoundingClientRect();
const imageAspect = nativeBounds.width / nativeBounds.height;
const imgWidth = Math.min(windowHeight, windowWidth * imageAspect);
const imgHeight = imgWidth / imageAspect;
const fullscreenWidth = isLandscape ? imgHeight : imgWidth;
const fullscreenHeight = isLandscape ? imgWidth : imgHeight;
const scale = fullscreenWidth / windowWidth;
const animation = (modal: HTMLElement, leave = false) => {
const root = modal.shadowRoot!;
const sourceAnimation = this.animationController
.create()
.addElement(this.elementRef.nativeElement)
.fromTo('opacity', 0, 0);
const backdrop =
'linear-gradient(to bottom,' +
'transparent 16px,' +
'rgba(0, 0, 0, 0.3) 20%,' +
'rgba(0, 0, 0, 0.3) 80%,' +
'transparent 100%)';
const wrapperAnimation = this.animationController
.create()
.beforeStyles({'--background': 'transparent', 'margin': '0'})
.addElement(root.querySelector('.modal-wrapper')!)
.fromTo('transform', 'scale(1)', 'scale(1)')
.fromTo('opacity', '1', '1');
const backdropAnimation = this.animationController
.create()
.beforeStyles({background: backdrop, filter: 'blur(16px)'})
.addElement(root.querySelector('ion-backdrop')!)
.fromTo('opacity', leave ? 1 : 0, leave ? 0 : 1);
const small =
`translate(${nativeBounds.left - safeAreaLeft}px, ${nativeBounds.top - safeAreaTop}px) ` +
`scale(${nativeBounds.width / windowWidth}) ` +
`rotate(0) `;
const large =
`translate(${windowWidth - (windowWidth - fullscreenHeight) / 2}px, ${
(windowHeight - fullscreenWidth) / 2
}px)` +
`scale(${scale}) ` +
`rotate(${isLandscape ? 0 : 90}deg) `;
const cardAnimation = this.animationController
.create()
.addElement(modal.querySelector('stapps-id-card')!)
.beforeStyles({
'transform-origin': 'top left',
'filter': 'drop-shadow(0 0 16px rgba(0, 0, 0, 0.1))',
})
.fromTo('transform', leave ? large : small, leave ? small : large);
return this.animationController
.create()
.addElement(modal)
.easing(mode === 'ios' ? iosEasing : mdEasing)
.duration(2 * (mode === 'ios' ? iosDuration : mdDuration))
.addAnimation([wrapperAnimation, backdropAnimation, cardAnimation, sourceAnimation]);
};
const modal = await this.modalController.create({
component: IdCardComponent,
backdropDismiss: true,
mode: 'md',
componentProps: {
item: this.item,
},
presentingElement: this.elementRef.nativeElement,
enterAnimation: base => animation(base),
leaveAnimation: base => animation(base, true),
});
const dismiss = () => modal.dismiss();
window.addEventListener('click', dismiss);
modal.addEventListener('didDismiss', () => window.removeEventListener('click', dismiss));
if (Capacitor.isNativePlatform()) {
const brightness = (await ScreenBrightness.getBrightness()).brightness;
ScreenBrightness.setBrightness({brightness: 1});
modal.addEventListener('didDismiss', () => {
ScreenOrientation.unlock();
ScreenBrightness.setBrightness({brightness: brightness === 1 ? 0.5 : brightness});
});
}
await modal.present();
}
}

View File

@@ -1,4 +1,9 @@
<img [src]="item.image" [alt]="'name' | thingTranslate: item" draggable="false" />
@if (item.validity && (item.validity | toDateRange | isInRangeNow | async) === false) {
<img
[src]="item.image"
[alt]="'name' | thingTranslate: item"
draggable="false"
(click)="presentFullscreen()"
/>
@if (item.validity && (item.validity | rangeToDateInterval | dfnsIntervalIsNow | async) === false) {
<div class="expired">{{ 'profile.userInfo.expired' | translate | titlecase }}</div>
}

View File

@@ -3,11 +3,6 @@
overflow: hidden;
}
:host:fullscreen {
margin: 0;
padding: 0;
}
img {
border-radius: 3mm;
}

View File

@@ -19,7 +19,6 @@ import {IonicModule} from '@ionic/angular';
import {AsyncPipe, TitleCasePipe} from '@angular/common';
import {ThingTranslateModule} from '../../translation/thing-translate.module';
import {UtilModule} from '../../util/util.module';
import {FullScreenImageDirective} from '../../util/full-screen-image.directive';
import {IdCardComponent} from './id-card.component';
import {TranslateModule} from '@ngx-translate/core';
import {Observable} from 'rxjs';
@@ -36,7 +35,6 @@ import {Observable} from 'rxjs';
AsyncPipe,
ThingTranslateModule,
UtilModule,
FullScreenImageDirective,
IdCardComponent,
TranslateModule,
TitleCasePipe,

View File

@@ -1,10 +1,11 @@
import {Injectable} from '@angular/core';
import {SCIdCard, SCThingOriginType, SCThingType, SCUserConfiguration} from '@openstapps/core';
import {from, Observable, of} from 'rxjs';
import {from, of, Observable} from 'rxjs';
import {AuthHelperService} from '../auth/auth-helper.service';
import {mergeMap, filter, map, startWith} from 'rxjs/operators';
import {mergeMap, concatWith, filter, map, startWith, catchError, tap} from 'rxjs/operators';
import {ConfigProvider} from '../config/config.provider';
import {HttpClient} from '@angular/common/http';
import {EncryptedStorageProvider} from '../storage/encrypted-storage.provider';
@Injectable({providedIn: 'root'})
export class IdCardsProvider {
@@ -12,18 +13,27 @@ export class IdCardsProvider {
private authHelper: AuthHelperService,
private config: ConfigProvider,
private httpClient: HttpClient,
private encryptedStorageProvider: EncryptedStorageProvider,
) {}
getIdCards(): Observable<SCIdCard[]> {
const feature = this.config.config.app.features.extern?.['idCards'];
const auth = this.authHelper.getProvider(feature?.authProvider ?? 'default');
const storedIdCards = from(
this.encryptedStorageProvider.get<SCIdCard[]>('id-cards') as Promise<SCIdCard[]>,
).pipe(filter(it => it !== undefined));
return auth.isAuthenticated$.pipe(
mergeMap(isAuthenticated =>
isAuthenticated
? feature
? from(auth.getValidToken()).pipe(
mergeMap(token => this.fetchIdCards(feature.url, token.accessToken)),
? storedIdCards.pipe(
concatWith(
from(auth.getValidToken()).pipe(
mergeMap(token => this.fetchIdCards(feature.url, token.accessToken)),
catchError(() => storedIdCards),
),
),
)
: auth.user$.pipe(
filter(user => user !== undefined),
@@ -31,19 +41,20 @@ export class IdCardsProvider {
mergeMap(user => this.fetchFallbackIdCards(user)),
startWith([]),
)
: // TODO: find a better solution here (async pipe stuff...)
of([]),
: of([]).pipe(tap(() => this.encryptedStorageProvider.delete('id-cards'))),
),
);
}
private fetchIdCards(url: string, token: string): Observable<SCIdCard[]> {
return this.httpClient.get<SCIdCard[]>(url, {
headers: {
Authorization: `Bearer ${token}`,
},
responseType: 'json',
});
return this.httpClient
.get<SCIdCard[]>(url, {
headers: {
Authorization: `Bearer ${token}`,
},
responseType: 'json',
})
.pipe(tap(idCards => this.encryptedStorageProvider.set('id-cards', idCards)));
}
private fetchFallbackIdCards(user: SCUserConfiguration): Observable<SCIdCard[]> {

View File

@@ -4,6 +4,7 @@ import {HttpClient} from '@angular/common/http';
import {AuthHelperService} from '../auth/auth-helper.service';
import {BehaviorSubject, firstValueFrom, of} from 'rxjs';
import {SCAuthorizationProviderType} from '@openstapps/core';
import {EncryptedStorageProvider} from '../storage/encrypted-storage.provider';
class FakeAuth {
isAuthenticated$ = new BehaviorSubject(false);
@@ -16,6 +17,7 @@ describe('IdCards', () => {
let configProvider: ConfigProvider;
let httpClient: HttpClient;
let authHelper: AuthHelperService;
let encryptedStorageProvider: EncryptedStorageProvider;
let fakeAuth: FakeAuth;
beforeEach(() => {
@@ -27,10 +29,14 @@ describe('IdCards', () => {
fakeAuth = new FakeAuth();
authHelper = jasmine.createSpyObj('AuthHelperService', ['getProvider']);
authHelper.getProvider = jasmine.createSpy().and.returnValue(fakeAuth);
encryptedStorageProvider = jasmine.createSpyObj('EncryptedStorageProvider', ['get', 'set', 'delete']);
encryptedStorageProvider.get = jasmine.createSpy().and.resolveTo();
encryptedStorageProvider.set = jasmine.createSpy().and.resolveTo();
encryptedStorageProvider.delete = jasmine.createSpy().and.resolveTo();
});
it('should emit undefined if not logged in', async () => {
const provider = new IdCardsProvider(authHelper, configProvider, httpClient);
const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
expect(await firstValueFrom(provider.getIdCards())).toEqual([]);
expect(authHelper.getProvider).toHaveBeenCalledOnceWith('fakeAuth' as SCAuthorizationProviderType);
});
@@ -39,7 +45,7 @@ describe('IdCards', () => {
fakeAuth.isAuthenticated$.next(true);
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
const provider = new IdCardsProvider(authHelper, configProvider, httpClient);
const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
expect(await firstValueFrom(provider.getIdCards())).toEqual(['abc' as never]);
expect(authHelper.getProvider).toHaveBeenCalledOnceWith('fakeAuth' as SCAuthorizationProviderType);
// eslint-disable-next-line unicorn/no-null
@@ -52,7 +58,7 @@ describe('IdCards', () => {
});
it('should react to logins', async () => {
const provider = new IdCardsProvider(authHelper, configProvider, httpClient);
const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
const observable = provider.getIdCards();
expect(await firstValueFrom(observable)).toEqual([]);
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));

View File

@@ -0,0 +1,92 @@
import {Injectable} from '@angular/core';
import {StorageProvider} from './storage.provider';
import {Capacitor} from '@capacitor/core';
import {SecureStoragePlugin} from 'capacitor-secure-storage-plugin';
@Injectable({providedIn: 'root'})
export class EncryptedStorageProvider {
constructor(private storageProvider: StorageProvider) {}
/**
* Retrieve a large value from an encrypted storage
* Also returns undefined if a secure context is not available (i.e. web).
* @param key Unique identifier of the wanted resource in storage
* @returns The value of the resource, if found
*/
async get<T>(key: string): Promise<T | undefined> {
if (!Capacitor.isNativePlatform()) return undefined;
try {
const jwt = JSON.parse((await SecureStoragePlugin.get({key: `stapps:key:${key}`})).value);
const aesKey = await crypto.subtle.importKey('jwk', jwt, {name: 'AES-GCM'}, true, [
'encrypt',
'decrypt',
]);
const iv = await this.storageProvider.get<ArrayBuffer>(`encrypted:${key}:iv`);
const encryptedIdCards = await this.storageProvider.get<ArrayBuffer>(`encrypted:${key}`);
const decrypted = await crypto.subtle.decrypt({name: 'AES-GCM', iv}, aesKey, encryptedIdCards);
const decompressionStream = new DecompressionStream('gzip');
const writer = decompressionStream.writable.getWriter();
writer.write(decrypted);
writer.close();
const decompressed = await new Response(decompressionStream.readable).arrayBuffer();
return JSON.parse(new TextDecoder().decode(decompressed));
} catch (error) {
console.warn(error);
return undefined;
}
}
/**
* Store a large value in an encrypted storage
* Does nothing if a secure context is not available (i.e. web).
* @param key Unique identifier of the resource in storage
* @param value The value to store
* @returns A promise that resolves when the value is stored
*/
async set<T>(key: string, value: T): Promise<void> {
if (!Capacitor.isNativePlatform()) return undefined;
try {
const compressionStream = new CompressionStream('gzip');
const writer = compressionStream.writable.getWriter();
writer.write(new TextEncoder().encode(JSON.stringify(value)));
writer.close();
const encoded = await new Response(compressionStream.readable).arrayBuffer();
const iv = crypto.getRandomValues(new Uint8Array(16));
const aesKey = await crypto.subtle.generateKey({name: 'AES-GCM', length: 256}, true, [
'encrypt',
'decrypt',
]);
await Promise.all([
SecureStoragePlugin.set({
key: `stapps:key:${key}`,
value: JSON.stringify(await crypto.subtle.exportKey('jwk', aesKey)),
}),
this.storageProvider.put(`encrypted:${key}:iv`, iv),
]);
this.storageProvider.put<ArrayBuffer>(
`encrypted:${key}`,
await crypto.subtle.encrypt({name: 'AES-GCM', iv}, aesKey, encoded),
);
} catch (error) {
alert(error);
}
}
async delete(key: string): Promise<void> {
if (!Capacitor.isNativePlatform()) return;
await Promise.all([
SecureStoragePlugin.remove({key: `stapps:key:${key}`}),
this.storageProvider.delete(`encrypted:${key}:iv`),
this.storageProvider.delete(`encrypted:${key}`),
]);
}
}

View File

@@ -1,7 +1,7 @@
import {Pipe, PipeTransform} from '@angular/core';
import {SCRange, isInRange, SCISO8601DateRange} from '@openstapps/core';
import {merge, Observable, timer} from 'rxjs';
import {distinctUntilChanged, map, startWith} from 'rxjs/operators';
import {NormalizedInterval, differenceInMilliseconds, interval, isEqual} from 'date-fns';
import {EMPTY, Observable, SchedulerLike, asyncScheduler, concat, defer, map, of, timer} from 'rxjs';
@Pipe({
name: 'isInRange',
@@ -14,28 +14,49 @@ export class InRangePipe implements PipeTransform {
}
}
export const MIN_DATE = new Date(0);
export const MAX_DATE = new Date(1e15);
@Pipe({
name: 'toDateRange',
name: 'rangeToDateInterval',
pure: true,
standalone: true,
})
export class ToDateRangePipe implements PipeTransform {
transform(value: SCISO8601DateRange): SCRange<Date> {
return Object.fromEntries(Object.entries(value).map(([key, value]) => [key, new Date(value)]));
export class ToDateIntervalPipe implements PipeTransform {
transform(value: SCISO8601DateRange): NormalizedInterval {
return interval(new Date(value.gte ?? value.gt ?? MIN_DATE), new Date(value.lte ?? value.lt ?? MAX_DATE));
}
}
/**
* Returns an Observable that will change its value when the current date is within the given interval.
*/
export function isWithinIntervalObservable(
value: NormalizedInterval,
scheduler: SchedulerLike = asyncScheduler,
): Observable<boolean> {
return defer(() => {
const now = scheduler.now();
const activate = differenceInMilliseconds(value.start, now);
const deactivate = differenceInMilliseconds(value.end, now);
return concat(
of(activate <= 0 && deactivate > 0),
activate <= 0 ? EMPTY : timer(value.start, scheduler).pipe(map(() => true)),
isEqual(value.end, MAX_DATE) || deactivate <= 0
? EMPTY
: timer(value.end, scheduler).pipe(map(() => false)),
);
});
}
@Pipe({
name: 'isInRangeNow',
name: 'dfnsIntervalIsNow',
pure: true,
standalone: true,
})
export class InRangeNowPipe implements PipeTransform {
transform(value: SCRange<Date>): Observable<boolean> {
return merge(timer(value.lte || value.lt || 0), timer(value.gte || value.gt || 0)).pipe(
startWith(0),
map(() => isInRange(new Date(), value)),
distinctUntilChanged(),
);
export class IntervalIsNowPipe implements PipeTransform {
transform(value: NormalizedInterval): Observable<boolean> {
return isWithinIntervalObservable(value);
}
}

View File

@@ -0,0 +1,42 @@
import {TestScheduler} from 'rxjs/testing';
import {MAX_DATE, MIN_DATE, isWithinIntervalObservable} from './in-range.pipe';
import {interval} from 'date-fns';
/**
* Test macro
*/
function test(range: [number | undefined, number | undefined], subscribe: string, expected: string) {
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).withContext(actual.map(JSON.stringify).join('\n')).toEqual(expected);
});
it(`should emit "${expected}" when "${subscribe}" for range ${range[0] ?? ''}..${range[1] ?? ''}`, () => {
testScheduler.run(({expectObservable}) => {
expectObservable(
isWithinIntervalObservable(
interval(new Date(range[0] ?? MIN_DATE), new Date(range[1] ?? MAX_DATE)),
testScheduler,
),
subscribe,
).toBe(expected, {t: true, f: false});
});
});
}
describe('isWithinIntervalObservable', () => {
test([500, undefined], '1s ^', '1s (t|)');
test([1000, undefined], '500ms ^', '500ms f 499ms (t|)');
test([undefined, 500], '1s ^', '1s (f|)');
test([undefined, 1000], '500ms ^', '500ms t 499ms (f|)');
test([1000, 2000], '500ms ^', '500ms f 499ms t 999ms (f|)');
test([500, 1000], '1500ms ^', '1500ms (f|)');
test([500, 1000], '1s ^', '1000ms (f|)');
test([500, 1000], '999ms ^', '999ms t (f|)');
test([500, 1000], '500ms ^', '500ms t 499ms (f|)');
test([500, 1000], '499ms ^', '499ms f t 499ms (f|)');
test([500, 1000], '^ 750ms !', 'f 499ms t');
});

View File

@@ -336,11 +336,15 @@
"title": "Titel",
"about": "Mehr Informationen",
"label": "Signatur",
"starttime": "Vorgemerkt am",
"endtime": "Abzuholen bis",
"storage": "Abholbereit"
"storage": "Abholtheke",
"queue": "Position in der Warteschlange"
},
"holds": "Bestellungen",
"reservations": "Vormerkungen"
"reservations": "Vormerkungen",
"ordered": "Bestellt",
"ready": "Abholbereit"
},
"checked_out": {
"title": "Deine Ausleihen",
@@ -348,7 +352,7 @@
"title": "Titel",
"about": "Mehr Informationen",
"label": "Signatur",
"endtime": "Leihfristende",
"duedate": "Leihfristende",
"renewals": "Verlängerungen"
}
},
@@ -418,7 +422,8 @@
"placeholder": "Veranstaltungen, Personen, Orte und mehr"
},
"instruction": "Finde alle Informationen rund ums Studium und den Campus",
"nothing_found": "Keine Ergebnisse"
"nothing_found": "Keine Ergebnisse",
"SUGGESTIONS": "Meintest du"
},
"hebisSearch": {
"title": "Bibliothekssuche",

View File

@@ -336,11 +336,15 @@
"title": "Title",
"about": "More information",
"label": "Shelfmark",
"starttime": "Reserved on",
"endtime": "Available for pickup until",
"storage": "Available for pickup"
"storage": "Pickup counter",
"queue": "Position in the queue"
},
"holds": "orders",
"reservations": "reservations"
"reservations": "reservations",
"ordered": "Ordered",
"ready": "Ready for pickup"
},
"checked_out": {
"title": "checked out items",
@@ -348,7 +352,7 @@
"title": "Title",
"about": "More information",
"label": "Label",
"endtime": "Due date",
"duedate": "Due date",
"renewals": "Renewals"
}
},
@@ -418,7 +422,8 @@
"placeholder": "Events, places, persons and more"
},
"instruction": "Find all information related to your studies and campus",
"nothing_found": "No results"
"nothing_found": "No results",
"SUGGESTIONS": "Did you mean"
},
"hebisSearch": {
"title": "Library Search",

View File

@@ -21,7 +21,7 @@ export const environment = {
backend_url: 'https://mobile.server.uni-frankfurt.de',
app_host: 'mobile.app.uni-frankfurt.de',
custom_url_scheme: 'de.anyschool.app',
backend_version: '3.1.0',
backend_version: '3.3.0',
production: true,
};

View File

@@ -18,10 +18,10 @@
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
backend_url: 'https://mobile.server.uni-frankfurt.de',
backend_url: 'http://localhost:3000',
app_host: 'mobile.app.uni-frankfurt.de',
custom_url_scheme: 'de.anyschool.app',
backend_version: '3.1.0',
backend_version: '999.0.0',
production: false,
};

View File

@@ -18,15 +18,15 @@
@import './dark';
@function to-rgb($color) {
@return red($color) + ',' + green($color) + ',' + blue($color);
@return color.red($color) + ',' + color.green($color) + ',' + color.blue($color);
}
@function to-shade($color) {
@return darken($color, $shade-amount);
@return color.adjust($color, $lightness: -$shade-amount);
}
@function to-tint($color) {
@return lighten($color, $tint-amount);
@return color.adjust($color, $lightness: $tint-amount);
}
@function to-contrast($color) {
@@ -35,7 +35,11 @@
}
@function fade($color, $amount) {
@return if(lightness($color) > $fade-threshold, darken($color, $amount), lighten($color, $amount));
@return if(
color.lightness($color) > $fade-threshold,
color.adjust($color, $lightness: -$amount),
color.adjust($color, $lightness: $amount)
);
}
@function map-range($value, $input-min, $input-max, $output-min, $output-max) {
@@ -44,9 +48,9 @@
@function interpolate-colors($from, $to, $progress, $progress-min: 0, $progress-max: 100) {
@return rgb(
map-range($progress, $progress-min, $progress-max, red($from), red($to)),
map-range($progress, $progress-min, $progress-max, green($from), green($to)),
map-range($progress, $progress-min, $progress-max, blue($from), blue($to))
map-range($progress, $progress-min, $progress-max, color.red($from), color.red($to)),
map-range($progress, $progress-min, $progress-max, color.green($from), color.green($to)),
map-range($progress, $progress-min, $progress-max, color.blue($from), color.blue($to))
);
}

View File

@@ -6,9 +6,9 @@ LABEL version="2.0.0" \
maintainer="Jovan Krunić <krunic@uni-frankfurt.de>"
### Configure versions to install
ENV ANDROID_APIS="android-33" \
ANDROID_BUILD_TOOLS_VERSION="30.0.2" \
NPM_VERSION="^9.0.0" \
ENV ANDROID_APIS="android-34" \
ANDROID_BUILD_TOOLS_VERSION="34.0.0" \
NPM_VERSION="^10.0.0" \
IONIC_VERSION="^6.0.0" \
CORDOVA_RES_VERSION="latest" \
### Configure download URLs

View File

@@ -1,372 +1,108 @@
#!/bin/bash
# Discussion, issues and change requests at:
# https://github.com/nodesource/distributions
#
# Script to install the NodeSource Node.js 18.x repo onto a
# Debian or Ubuntu system.
#
# Run as root or insert `sudo -E` before `bash`:
#
# curl -sL https://deb.nodesource.com/setup_18.x | bash -
# or
# wget -qO- https://deb.nodesource.com/setup_18.x | bash -
#
# CONTRIBUTIONS TO THIS SCRIPT
#
# This script is built from a template in
# https://github.com/nodesource/distributions/tree/master/deb/src
# please don't submit pull requests against the built scripts.
#
# Logger Function
log() {
local message="$1"
local type="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local color
local endcolor="\033[0m"
case "$type" in
"info") color="\033[38;5;79m" ;;
"success") color="\033[1;32m" ;;
"error") color="\033[1;31m" ;;
*) color="\033[1;34m" ;;
esac
export DEBIAN_FRONTEND=noninteractive
SCRSUFFIX="_18.x"
NODENAME="Node.js 18.x"
NODEREPO="node_18.x"
NODEPKG="nodejs"
print_status() {
echo
echo "## $1"
echo
echo -e "${color}${timestamp} - ${message}${endcolor}"
}
if test -t 1; then # if terminal
ncolors=$(which tput > /dev/null && tput colors) # supports color
if test -n "$ncolors" && test $ncolors -ge 8; then
termcols=$(tput cols)
bold="$(tput bold)"
underline="$(tput smul)"
standout="$(tput smso)"
normal="$(tput sgr0)"
black="$(tput setaf 0)"
red="$(tput setaf 1)"
green="$(tput setaf 2)"
yellow="$(tput setaf 3)"
blue="$(tput setaf 4)"
magenta="$(tput setaf 5)"
cyan="$(tput setaf 6)"
white="$(tput setaf 7)"
fi
fi
print_bold() {
title="$1"
text="$2"
echo
echo "${red}================================================================================${normal}"
echo "${red}================================================================================${normal}"
echo
echo -e " ${bold}${yellow}${title}${normal}"
echo
echo -en " ${text}"
echo
echo "${red}================================================================================${normal}"
echo "${red}================================================================================${normal}"
# Error handler function
handle_error() {
local exit_code=$1
local error_message="$2"
log "Error: $error_message (Exit Code: $exit_code)" "error"
exit $exit_code
}
bail() {
echo 'Error executing command, exiting'
exit 1
# Function to check for command availability
command_exists() {
command -v "$1" &> /dev/null
}
exec_cmd_nobail() {
echo "+ $1"
bash -c "$1"
}
exec_cmd() {
exec_cmd_nobail "$1" || bail
}
node_deprecation_warning() {
if [[ "X${NODENAME}" == "Xio.js 1.x" ||
"X${NODENAME}" == "Xio.js 2.x" ||
"X${NODENAME}" == "Xio.js 3.x" ||
"X${NODENAME}" == "XNode.js 0.10" ||
"X${NODENAME}" == "XNode.js 0.12" ||
"X${NODENAME}" == "XNode.js 4.x LTS Argon" ||
"X${NODENAME}" == "XNode.js 5.x" ||
"X${NODENAME}" == "XNode.js 6.x LTS Boron" ||
"X${NODENAME}" == "XNode.js 7.x" ||
"X${NODENAME}" == "XNode.js 8.x LTS Carbon" ||
"X${NODENAME}" == "XNode.js 9.x" ||
"X${NODENAME}" == "XNode.js 10.x" ||
"X${NODENAME}" == "XNode.js 11.x" ||
"X${NODENAME}" == "XNode.js 12.x" ||
"X${NODENAME}" == "XNode.js 13.x" ||
"X${NODENAME}" == "XNode.js 14.x" ||
"X${NODENAME}" == "XNode.js 15.x" ||
"X${NODENAME}" == "XNode.js 17.x" ]]; then
print_bold \
" DEPRECATION WARNING " "\
${bold}${NODENAME} is no longer actively supported!${normal}
${bold}You will not receive security or critical stability updates${normal} for this version.
You should migrate to a supported version of Node.js as soon as possible.
Use the installation script that corresponds to the version of Node.js you
wish to install. e.g.
* ${green}https://deb.nodesource.com/setup_16.x — Node.js 16 \"Gallium\"${normal}
* ${green}https://deb.nodesource.com/setup_18.x — Node.js 18 LTS \"Hydrogen\"${normal} (recommended)
* ${green}https://deb.nodesource.com/setup_19.x — Node.js 19 \"Nineteen\"${normal}
* ${green}https://deb.nodesource.com/setup_20.x — Node.js 20 \"Iron\"${normal} (current)
Please see ${bold}https://github.com/nodejs/Release${normal} for details about which
version may be appropriate for you.
The ${bold}NodeSource${normal} Node.js distributions repository contains
information both about supported versions of Node.js and supported Linux
distributions. To learn more about usage, see the repository:
${bold}https://github.com/nodesource/distributions${normal}
"
echo
echo "Continuing in 20 seconds ..."
echo
sleep 20
check_os() {
if ! [ -f "/etc/debian_version" ]; then
echo "Error: This script is only supported on Debian-based systems."
exit 1
fi
}
script_deprecation_warning() {
if [ "X${SCRSUFFIX}" == "X" ]; then
print_bold \
" SCRIPT DEPRECATION WARNING " "\
This script, located at ${bold}https://deb.nodesource.com/setup${normal}, used to
install Node.js 0.10, is deprecated and will eventually be made inactive.
# Function to Install the script pre-requisites
install_pre_reqs() {
log "Installing pre-requisites" "info"
You should use the script that corresponds to the version of Node.js you
wish to install. e.g.
# Run 'apt-get update'
if ! apt-get update -y; then
handle_error "$?" "Failed to run 'apt-get update'"
fi
* ${green}https://deb.nodesource.com/setup_16.x — Node.js 16 \"Gallium\"${normal}
* ${green}https://deb.nodesource.com/setup_18.x — Node.js 18 LTS \"Hydrogen\"${normal} (recommended)
* ${green}https://deb.nodesource.com/setup_19.x — Node.js 19 \"Nineteen\"${normal}
* ${green}https://deb.nodesource.com/setup_20.x — Node.js 20 \"Iron\"${normal} (current)
# Run 'apt-get install'
if ! apt-get install -y apt-transport-https ca-certificates curl gnupg; then
handle_error "$?" "Failed to install packages"
fi
Please see ${bold}https://github.com/nodejs/Release${normal} for details about which
version may be appropriate for you.
if ! mkdir -p /usr/share/keyrings; then
handle_error "$?" "Makes sure the path /usr/share/keyrings exist or run ' mkdir -p /usr/share/keyrings' with sudo"
fi
The ${bold}NodeSource${normal} Node.js Linux distributions GitHub repository contains
information about which versions of Node.js and which Linux distributions
are supported and how to use the install scripts.
${bold}https://github.com/nodesource/distributions${normal}
"
rm -f /usr/share/keyrings/nodesource.gpg || true
rm -f /etc/apt/sources.list.d/nodesource.list || true
echo
echo "Continuing in 20 seconds (press Ctrl-C to abort) ..."
echo
sleep 20
# Run 'curl' and 'gpg'
if ! curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource.gpg; then
handle_error "$?" "Failed to download and import the NodeSource signing key"
fi
}
setup() {
# Function to configure the Repo
configure_repo() {
local node_version=$1
script_deprecation_warning
node_deprecation_warning
arch=$(dpkg --print-architecture)
if [ "$arch" != "amd64" ] && [ "$arch" != "arm64" ] && [ "$arch" != "armhf" ]; then
handle_error "1" "Unsupported architecture: $arch. Only amd64, arm64, and armhf are supported."
fi
print_status "Installing the NodeSource ${NODENAME} repo..."
echo "deb [arch=$arch signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$node_version nodistro main" | tee /etc/apt/sources.list.d/nodesource.list > /dev/null
if $(uname -m | grep -Eq ^armv6); then
print_status "You appear to be running on ARMv6 hardware. Unfortunately this is not currently supported by the NodeSource Linux distributions. Please use the 'linux-armv6l' binary tarballs available directly from nodejs.org for Node.js 4 and later."
exit 1
fi
# N|solid Config
echo "Package: nsolid" | tee /etc/apt/preferences.d/nsolid > /dev/null
echo "Pin: origin deb.nodesource.com" | tee -a /etc/apt/preferences.d/nsolid > /dev/null
echo "Pin-Priority: 600" | tee -a /etc/apt/preferences.d/nsolid > /dev/null
PRE_INSTALL_PKGS=""
# Nodejs Config
echo "Package: nodejs" | tee /etc/apt/preferences.d/nodejs > /dev/null
echo "Pin: origin deb.nodesource.com" | tee -a /etc/apt/preferences.d/nodejs > /dev/null
echo "Pin-Priority: 600" | tee -a /etc/apt/preferences.d/nodejs > /dev/null
# Check that HTTPS transport is available to APT
# (Check snaked from: https://get.docker.io/ubuntu/)
if [ ! -e /usr/lib/apt/methods/https ]; then
PRE_INSTALL_PKGS="${PRE_INSTALL_PKGS} apt-transport-https"
fi
if [ ! -x /usr/bin/lsb_release ]; then
PRE_INSTALL_PKGS="${PRE_INSTALL_PKGS} lsb-release"
fi
if [ ! -x /usr/bin/curl ] && [ ! -x /usr/bin/wget ]; then
PRE_INSTALL_PKGS="${PRE_INSTALL_PKGS} curl"
fi
# Used by apt-key to add new keys
if [ ! -x /usr/bin/gpg ]; then
PRE_INSTALL_PKGS="${PRE_INSTALL_PKGS} gnupg"
fi
# Populating Cache
print_status "Populating apt-get cache..."
exec_cmd 'apt-get update'
if [ "X${PRE_INSTALL_PKGS}" != "X" ]; then
print_status "Installing packages required for setup:${PRE_INSTALL_PKGS}..."
# This next command needs to be redirected to /dev/null or the script will bork
# in some environments
exec_cmd "apt-get install -y${PRE_INSTALL_PKGS} > /dev/null 2>&1"
fi
IS_PRERELEASE=$(lsb_release -d | grep 'Ubuntu .*development' >& /dev/null; echo $?)
if [[ $IS_PRERELEASE -eq 0 ]]; then
print_status "Your distribution, identified as \"$(lsb_release -d -s)\", is a pre-release version of Ubuntu. NodeSource does not maintain official support for Ubuntu versions until they are formally released. You can try using the manual installation instructions available at https://github.com/nodesource/distributions and use the latest supported Ubuntu version name as the distribution identifier, although this is not guaranteed to work."
exit 1
fi
DISTRO=$(lsb_release -c -s)
check_alt() {
if [ "X${DISTRO}" == "X${2}" ]; then
echo
echo "## You seem to be using ${1} version ${DISTRO}."
echo "## This maps to ${3} \"${4}\"... Adjusting for you..."
DISTRO="${4}"
# Run 'apt-get update'
if ! apt-get update -y; then
handle_error "$?" "Failed to run 'apt-get update'"
else
log "Repository configured successfully."
log "To install Node.js, run: apt-get install nodejs -y" "info"
log "You can use N|solid Runtime as a node.js alternative" "info"
log "To install N|solid Runtime, run: apt-get install nsolid -y \n" "success"
fi
}
check_alt "Astra Linux" "orel" "Debian" "stretch"
check_alt "BOSS" "anokha" "Debian" "wheezy"
check_alt "BOSS" "anoop" "Debian" "jessie"
check_alt "BOSS" "drishti" "Debian" "stretch"
check_alt "BOSS" "unnati" "Debian" "buster"
check_alt "BOSS" "urja" "Debian" "bullseye"
check_alt "bunsenlabs" "bunsen-hydrogen" "Debian" "jessie"
check_alt "bunsenlabs" "helium" "Debian" "stretch"
check_alt "bunsenlabs" "lithium" "Debian" "buster"
check_alt "Devuan" "jessie" "Debian" "jessie"
check_alt "Devuan" "ascii" "Debian" "stretch"
check_alt "Devuan" "beowulf" "Debian" "buster"
check_alt "Devuan" "chimaera" "Debian" "bullseye"
check_alt "Devuan" "ceres" "Debian" "sid"
check_alt "Devuan" "daedalus" "Debian" "bookworm"
check_alt "Deepin" "panda" "Debian" "sid"
check_alt "Deepin" "unstable" "Debian" "sid"
check_alt "Deepin" "stable" "Debian" "buster"
check_alt "Deepin" "apricot" "Debian" "buster"
check_alt "Deepin" "beige" "Debian" "bookworm"
check_alt "elementaryOS" "luna" "Ubuntu" "precise"
check_alt "elementaryOS" "freya" "Ubuntu" "trusty"
check_alt "elementaryOS" "loki" "Ubuntu" "xenial"
check_alt "elementaryOS" "juno" "Ubuntu" "bionic"
check_alt "elementaryOS" "hera" "Ubuntu" "bionic"
check_alt "elementaryOS" "odin" "Ubuntu" "focal"
check_alt "elementaryOS" "jolnir" "Ubuntu" "focal"
check_alt "elementaryOS" "horus" "Ubuntu" "jammy"
check_alt "Kali" "sana" "Debian" "jessie"
check_alt "Kali" "kali-rolling" "Debian" "bullseye"
check_alt "Linux Mint" "maya" "Ubuntu" "precise"
check_alt "Linux Mint" "qiana" "Ubuntu" "trusty"
check_alt "Linux Mint" "rafaela" "Ubuntu" "trusty"
check_alt "Linux Mint" "rebecca" "Ubuntu" "trusty"
check_alt "Linux Mint" "rosa" "Ubuntu" "trusty"
check_alt "Linux Mint" "sarah" "Ubuntu" "xenial"
check_alt "Linux Mint" "serena" "Ubuntu" "xenial"
check_alt "Linux Mint" "sonya" "Ubuntu" "xenial"
check_alt "Linux Mint" "sylvia" "Ubuntu" "xenial"
check_alt "Linux Mint" "tara" "Ubuntu" "bionic"
check_alt "Linux Mint" "tessa" "Ubuntu" "bionic"
check_alt "Linux Mint" "tina" "Ubuntu" "bionic"
check_alt "Linux Mint" "tricia" "Ubuntu" "bionic"
check_alt "Linux Mint" "ulyana" "Ubuntu" "focal"
check_alt "Linux Mint" "ulyssa" "Ubuntu" "focal"
check_alt "Linux Mint" "uma" "Ubuntu" "focal"
check_alt "Linux Mint" "una" "Ubuntu" "focal"
check_alt "Linux Mint" "vanessa" "Ubuntu" "jammy"
check_alt "Linux Mint" "vera" "Ubuntu" "jammy"
check_alt "Liquid Lemur" "lemur-3" "Debian" "stretch"
check_alt "LMDE" "betsy" "Debian" "jessie"
check_alt "LMDE" "cindy" "Debian" "stretch"
check_alt "LMDE" "debbie" "Debian" "buster"
check_alt "LMDE" "elsie" "Debian" "bullseye"
check_alt "MX Linux 17" "Horizon" "Debian" "stretch"
check_alt "MX Linux 18" "Continuum" "Debian" "stretch"
check_alt "MX Linux 19" "patito feo" "Debian" "buster"
check_alt "MX Linux 21" "wildflower" "Debian" "bullseye"
check_alt "Pardus" "onyedi" "Debian" "stretch"
check_alt "Parrot" "ara" "Debian" "bullseye"
check_alt "PureOS" "green" "Debian" "sid"
check_alt "PureOS" "amber" "Debian" "buster"
check_alt "PureOS" "byzantium" "Debian" "bullseye"
check_alt "SolydXK" "solydxk-9" "Debian" "stretch"
check_alt "Sparky Linux" "Tyche" "Debian" "stretch"
check_alt "Sparky Linux" "Nibiru" "Debian" "buster"
check_alt "Sparky Linux" "Po-Tolo" "Debian" "bullseye"
check_alt "Tanglu" "chromodoris" "Debian" "jessie"
check_alt "Trisquel" "toutatis" "Ubuntu" "precise"
check_alt "Trisquel" "belenos" "Ubuntu" "trusty"
check_alt "Trisquel" "flidas" "Ubuntu" "xenial"
check_alt "Trisquel" "etiona" "Ubuntu" "bionic"
check_alt "Ubilinux" "dolcetto" "Debian" "stretch"
check_alt "Uruk GNU/Linux" "lugalbanda" "Ubuntu" "xenial"
# Define Node.js version
NODE_VERSION="18.x"
if [ "X${DISTRO}" == "Xdebian" ]; then
print_status "Unknown Debian-based distribution, checking /etc/debian_version..."
NEWDISTRO=$([ -e /etc/debian_version ] && cut -d/ -f1 < /etc/debian_version)
if [ "X${NEWDISTRO}" == "X" ]; then
print_status "Could not determine distribution from /etc/debian_version..."
else
DISTRO=$NEWDISTRO
print_status "Found \"${DISTRO}\" in /etc/debian_version..."
fi
fi
# Check OS
check_os
print_status "Confirming \"${DISTRO}\" is supported..."
if [ -x /usr/bin/curl ]; then
exec_cmd_nobail "curl -sLf -o /dev/null 'https://deb.nodesource.com/${NODEREPO}/dists/${DISTRO}/Release'"
RC=$?
else
exec_cmd_nobail "wget -qO /dev/null -o /dev/null 'https://deb.nodesource.com/${NODEREPO}/dists/${DISTRO}/Release'"
RC=$?
fi
if [[ $RC != 0 ]]; then
print_status "Your distribution, identified as \"${DISTRO}\", is not currently supported, please contact NodeSource at https://github.com/nodesource/distributions/issues if you think this is incorrect or would like your distribution to be considered for support"
exit 1
fi
if [ -f "/etc/apt/sources.list.d/chris-lea-node_js-$DISTRO.list" ]; then
print_status 'Removing Launchpad PPA Repository for NodeJS...'
exec_cmd_nobail 'add-apt-repository -y -r ppa:chris-lea/node.js'
exec_cmd "rm -f /etc/apt/sources.list.d/chris-lea-node_js-${DISTRO}.list"
fi
print_status 'Adding the NodeSource signing key to your keyring...'
keyring='/usr/share/keyrings'
node_key_url="https://deb.nodesource.com/gpgkey/nodesource.gpg.key"
local_node_key="$keyring/nodesource.gpg"
if [ -x /usr/bin/curl ]; then
exec_cmd "curl -s $node_key_url | gpg --dearmor | tee $local_node_key >/dev/null"
else
exec_cmd "wget -q -O - $node_key_url | gpg --dearmor | tee $local_node_key >/dev/null"
fi
print_status "Creating apt sources list file for the NodeSource ${NODENAME} repo..."
exec_cmd "echo 'deb [signed-by=$local_node_key] https://deb.nodesource.com/${NODEREPO} ${DISTRO} main' > /etc/apt/sources.list.d/nodesource.list"
exec_cmd "echo 'deb-src [signed-by=$local_node_key] https://deb.nodesource.com/${NODEREPO} ${DISTRO} main' >> /etc/apt/sources.list.d/nodesource.list"
print_status 'Running `apt-get update` for you...'
exec_cmd 'apt-get update'
yarn_site='https://dl.yarnpkg.com/debian'
yarn_key_url="$yarn_site/pubkey.gpg"
local_yarn_key="$keyring/yarnkey.gpg"
print_status """Run \`${bold}sudo apt-get install -y ${NODEPKG}${normal}\` to install ${NODENAME} and npm
## You may also need development tools to build native addons:
sudo apt-get install gcc g++ make
## To install the Yarn package manager, run:
curl -sL $yarn_key_url | gpg --dearmor | sudo tee $local_yarn_key >/dev/null
echo \"deb [signed-by=$local_yarn_key] $yarn_site stable main\" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install yarn
"""
}
## Defer setup until we have the complete script
setup
# Main execution
install_pre_reqs || handle_error $? "Failed installing pre-requisites"
configure_repo "$NODE_VERSION" || handle_error $? "Failed configuring repository"

View File

@@ -2,8 +2,8 @@
"name": "@openstapps/openstapps",
"private": true,
"engines": {
"node": ">=18.16",
"pnpm": ">=8"
"node": "^18.19.1",
"pnpm": "^8.15.4"
},
"scripts": {
"build": "dotenv -c -- turbo run build",
@@ -13,11 +13,12 @@
"deploy": "dotenv -c -- turbo run deploy --concurrency=1",
"dev": "dotenv -c -- turbo run dev",
"docs": "dotenv -c -- turbo run docs && typedoc && mkdir docs/api && cp packages/core/lib/api-doc.html docs/api/index.html && cp packages/core/lib/openapi.json docs/api/openapi.json && cp -r packages/core/lib/schema docs/api/schema",
"docs:serve": "http-server docs -p 8080 -o",
"format": "dotenv -c -- turbo run format",
"format:fix": "dotenv -c -- turbo run format:fix",
"lint": "dotenv -c -- turbo run lint",
"lint:fix": "dotenv -c -- turbo run lint:fix",
"publish-packages": "pnpm changeset version && pnpm syncpack:fix && pnpm install && git add . && git commit -m \"docs: update changelogs for release\n\nci: publish release\" && git push && pnpm changeset tag && git push --follow-tags",
"publish-packages": "pnpm changeset version && pnpm syncpack:fix && pnpm install && pnpm format:fix && git add . && git commit -m \"docs: update changelogs for release\" -m \"ci: publish release\" && git push && pnpm changeset tag && git push --follow-tags",
"syncpack": "syncpack list-mismatches && syncpack lint-semver-ranges --types dev,peer,prod",
"syncpack:fix": "syncpack format && syncpack fix-mismatches",
"test": "trap 'node coverage.mjs' EXIT && dotenv -c -- turbo run test",
@@ -34,6 +35,7 @@
"deepmerge": "4.3.1",
"dotenv-cli": "7.2.1",
"glob": "10.3.10",
"http-server": "14.1.1",
"junit-report-merger": "6.0.3",
"prettier": "3.1.1",
"syncpack": "12.3.0",

View File

@@ -1,12 +1,35 @@
# @openstapps/api-cli
## 3.3.2
### Patch Changes
- @openstapps/core@3.3.2
- @openstapps/core-tools@3.3.2
- @openstapps/api@3.3.2
## 3.3.0
### Minor Changes
- 688bc5f2: v3.3.0 changes
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/api@3.3.0
- @openstapps/core@3.3.0
- @openstapps/eslint-config@3.0.0
- @openstapps/core-tools@3.3.0
- @openstapps/logger@3.0.0
## 3.2.0
### Patch Changes
- Updated dependencies [912ae422]
- @openstapps/core@4.0.0
- @openstapps/api@4.0.0
- @openstapps/core@3.2.0
- @openstapps/api@3.2.0
- @openstapps/core-tools@3.0.0
- @openstapps/logger@3.0.0

View File

@@ -23,7 +23,7 @@ node ./lib/cli.js e2e http://localhost:3000
Example to clone the full database
```shell
node app.js copy "*" https://mobile.app.uni-frankfurt.de http://localhost:3000 100
node app.js copy "*" -a "999.0.0" https://mobile.server.uni-frankfurt.de http://localhost:3000 100
```
### Program arguments

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/api-cli",
"description": "CLI client for @openstapps/api",
"version": "3.2.0",
"version": "3.3.2",
"type": "module",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/api.git",

View File

@@ -1,12 +1,30 @@
# @openstapps/api-plugin
## 3.3.2
### Patch Changes
- @openstapps/core@3.3.2
- @openstapps/core-tools@3.3.2
- @openstapps/api@3.3.2
## 3.3.0
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/api@3.3.0
- @openstapps/core@3.3.0
- @openstapps/core-tools@3.3.0
- @openstapps/logger@3.0.0
## 3.2.0
### Patch Changes
- Updated dependencies [912ae422]
- @openstapps/core@4.0.0
- @openstapps/api@4.0.0
- @openstapps/core@3.2.0
- @openstapps/api@3.2.0
- @openstapps/core-tools@3.0.0
- @openstapps/logger@3.0.0

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/api-plugin",
"description": "Node.js library to interact with the StApps backend service",
"version": "3.2.0",
"version": "3.3.2",
"type": "module",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/api.git",

View File

@@ -1,11 +1,28 @@
# @openstapps/api
## 3.3.2
### Patch Changes
- @openstapps/core@3.3.2
## 3.3.0
### Minor Changes
- 688bc5f2: v3.3.0 changes
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/core@3.3.0
## 3.2.0
### Patch Changes
- Updated dependencies [912ae422]
- @openstapps/core@4.0.0
- @openstapps/core@3.2.0
## 3.1.1

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/api",
"description": "Node.js library to interact with the StApps backend service",
"version": "3.2.0",
"version": "3.3.2",
"type": "module",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/api.git",

View File

@@ -1,5 +1,21 @@
# @openstapps/core-tools
## 3.3.2
### Patch Changes
- Updated dependencies [802a7a3f]
- @openstapps/easy-ast@3.3.2
## 3.3.0
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/easy-ast@3.3.0
- @openstapps/collection-utils@3.0.0
- @openstapps/logger@3.0.0
## 3.0.0
### Major Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/core-tools",
"description": "Tools to convert and validate StAppsCore",
"version": "3.0.0",
"version": "3.3.2",
"type": "module",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/core-tools.git",

View File

@@ -1,5 +1,21 @@
# @openstapps/core
## 3.3.2
### Patch Changes
- @openstapps/core-tools@3.3.2
## 3.3.0
### Minor Changes
- 688bc5f2: v3.3.0 changes
### Patch Changes
- @openstapps/core-tools@3.3.0
## 3.2.0
### Minor Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/core",
"description": "StAppsCore - Generalized model of data",
"version": "3.2.0",
"version": "3.3.2",
"type": "module",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/core.git",
@@ -97,6 +97,8 @@
"date",
"validatable",
"filterable",
"suggestable",
"completable",
"inheritTags",
"minLength",
"pattern",

View File

@@ -24,6 +24,11 @@ export interface SCSearchResult {
*/
data: SCThings[];
/**
* Suggestions for query corrections
*/
suggestions?: SCSearchSuggestions;
/**
* Facets (aggregations over all matching data)
*/
@@ -40,6 +45,18 @@ export interface SCSearchResult {
stats: SCSearchResultSearchEngineStats;
}
/**
* Seach suggestions
*
* Not to be confused with search-as-you-type suggestions
*/
export interface SCSearchSuggestions {
/**
* Suggestions for query terms that might have been misspelled
*/
terms?: Record<string, string[]>;
}
/**
* Stores information about Pagination
*/

View File

@@ -63,7 +63,7 @@ export interface SCThingWithoutReferences {
/**
* Alternate names of the thing
* @filterable
* @keyword
* @text
*/
alternateNames?: string[];
@@ -92,6 +92,8 @@ export interface SCThingWithoutReferences {
* @filterable
* @minLength 1
* @sortable ducet
* @completable
* @suggestable
* @text
*/
name: string;
@@ -240,6 +242,8 @@ export interface SCThingTranslatableProperties {
* Translation of the name of the thing
* @sortable ducet
* @text
* @suggestable
* @completable
*/
name?: string;
/**

View File

@@ -1,5 +1,22 @@
# @openstapps/easy-ast
## 3.3.2
### Patch Changes
- 802a7a3f: Fixed docs generation
## 3.3.0
### Minor Changes
- 688bc5f2: v3.3.0 changes
### Patch Changes
- @openstapps/collection-utils@3.0.0
- @openstapps/logger@3.0.0
## 3.0.0
### Major Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/easy-ast",
"description": "Tool to easily handle TypeScript AST",
"version": "3.0.0",
"version": "3.3.2",
"type": "module",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/core-tools.git",

View File

@@ -17,10 +17,8 @@ import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
import {TypeFlags} from 'typescript';
// @ts-expect-error unused type
type TestTypeAlias = number | string;
// @ts-expect-error unused type
enum TestEnum {
Foo,
Bar,

View File

@@ -18,13 +18,9 @@ import {LightweightDefinitionKind} from '../../src/index.js';
interface Random {}
// @ts-expect-error unused type
type TestArrayGeneric = Array<string>;
// @ts-expect-error unused type
type TestArrayLiteral = number[];
// @ts-expect-error unused type
type TestArrayReferenceGeneric = Array<Random>;
// @ts-expect-error unused type
type TestArrayReferenceLiteral = Random[];
export const testConfig: EasyAstSpecType = {

View File

@@ -16,12 +16,10 @@
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
// @ts-expect-error unused type
interface TestInterface {
foo: number;
}
// @ts-expect-error unused type
class TestClass {
bar: string = 'test';
}

View File

@@ -22,10 +22,8 @@ import {LightweightDefinitionKind} from '../../src/index.js';
* Class description
*
* More description
*
* @classTag classParameter1 classParameter2
*/
// @ts-expect-error unused type
interface TestInterface {
/**
* Property comment
@@ -33,7 +31,6 @@ interface TestInterface {
* Property description
*
* More description
*
* @propertyTag propertyParameter1 propertyParameter2
*/
foo: string;
@@ -45,10 +42,8 @@ interface TestInterface {
* Class description
*
* More description
*
* @classTag classParameter1 classParameter2
*/
// @ts-expect-error unused type
class TestClass {
/**
* Property comment
@@ -56,7 +51,6 @@ class TestClass {
* Property description
*
* More description
*
* @propertyTag propertyParameter1 propertyParameter2
*/
foo = 1;
@@ -68,10 +62,8 @@ class TestClass {
* Enum description
*
* More description
*
* @enumTag enumParameter1
*/
// @ts-expect-error unused type
enum TestAlias {}
export const testConfig: EasyAstSpecType = {

View File

@@ -20,7 +20,6 @@ interface Test1<T = number> {
foo: T;
}
// @ts-expect-error unused type
interface Test2 {
bar: Test1;
}

View File

@@ -17,13 +17,11 @@ import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
import {TypeFlags} from 'typescript';
// @ts-expect-error unused type
enum TestAuto {
Foo,
Bar,
}
// @ts-expect-error unused type
enum TestSpecified {
YES = 'yes',
NO = 'no',

View File

@@ -18,7 +18,6 @@ import {LightweightDefinitionKind} from '../../src/index.js';
interface $Random {}
// @ts-expect-error unused type
interface Generics {
baz: Foo<number, $Random>;
}

View File

@@ -18,12 +18,10 @@ import {LightweightDefinitionKind} from '../../src/index.js';
interface $Random {}
// @ts-expect-error unused
interface IndexSignatureObject {
[key: string]: $Random;
}
// @ts-expect-error unused
interface IndexSignaturePrimitive {
[key: string]: number;
}

View File

@@ -26,7 +26,6 @@ interface $BaseInterface2 {
class $BaseClass {}
// @ts-expect-error unused
class InheritingClass extends $BaseClass implements $BaseInterface<number>, $BaseInterface2 {
bar: string = '';

View File

@@ -16,7 +16,6 @@
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
// @ts-expect-error unused
interface NestedObject {
nested: {
deeplyNested: {

View File

@@ -17,7 +17,6 @@ import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
import {TypeFlags} from 'typescript';
// @ts-expect-error unused
interface Test {
number_type: number;
string_type: string;

View File

@@ -16,7 +16,6 @@
import {EasyAstSpecType} from '../easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/index.js';
// @ts-expect-error unused
interface Foo<T extends Bar<string>> {
bar: T;
}

View File

@@ -43,6 +43,27 @@ export const fieldmap: ElasticsearchFieldmap = {
},
ignore: ['price'],
},
suggestable: {
default: {
trigram: {
type: 'text',
analyzer: 'trigram',
},
reverse: {
type: 'text',
analyzer: 'reverse',
},
},
ignore: [],
},
completable: {
default: {
completion: {
type: 'search_as_you_type',
},
},
ignore: [],
},
};
export const filterableTagName = 'filterable';

View File

@@ -27,15 +27,7 @@ export const premaps: Record<string, MappingProperty> = {
type: 'geo_shape',
},
'Point': {
properties: {
type: {
type: 'keyword',
},
coordinates: {
type: 'geo_point',
},
},
dynamic: 'strict',
type: 'geo_point',
},
'Polygon': {
type: 'geo_shape',

Some files were not shown because too many files have changed in this diff Show More