mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 01:46:07 +00:00
Merge remote-tracking branch 'origin/master' into
3447-modify-humhub-card-in-overview-page
This commit is contained in:
commit
6f02396d73
16
.github/workflows/test_dht_node.yml
vendored
16
.github/workflows/test_dht_node.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
|
||||
- name: Build 'test' image
|
||||
run: |
|
||||
docker build --target test -t "gradido/dht-node:test" -f dht-node/Dockerfile .
|
||||
@ -49,7 +49,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
|
||||
- name: Lint
|
||||
run: cd database && yarn && cd ../config && yarn install && cd ../dht-node && yarn && yarn run lint
|
||||
|
||||
@ -61,7 +61,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
|
||||
- name: Download Docker Image
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@ -70,10 +70,10 @@ jobs:
|
||||
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/dht-node.tar
|
||||
|
||||
|
||||
- name: docker-compose mariadb
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mariadb
|
||||
|
||||
|
||||
- name: Sleep for 30 seconds
|
||||
run: sleep 30s
|
||||
shell: bash
|
||||
@ -85,7 +85,7 @@ jobs:
|
||||
run: sleep 30s
|
||||
shell: bash
|
||||
|
||||
#- name: Unit tests
|
||||
# run: cd database && yarn && yarn build && cd ../dht-node && yarn && yarn test
|
||||
- name: Unit tests
|
||||
run: docker run --env NODE_ENV=test --env DB_HOST=mariadb --network gradido_internal-net --rm gradido/dht-node:test yarn run test
|
||||
run: cd database && yarn && yarn build && cd ../config && yarn install && cd ../dht-node && yarn && yarn test
|
||||
#- name: Unit tests
|
||||
# run: docker run --env NODE_ENV=test --env DB_HOST=mariadb --network gradido_internal-net --rm gradido/dht-node:test yarn run test
|
||||
|
||||
@ -4,8 +4,15 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [2.4.5](https://github.com/gradido/gradido/compare/2.3.1...2.4.5)
|
||||
#### [2.5.1](https://github.com/gradido/gradido/compare/2.3.1...2.5.1)
|
||||
|
||||
- refactor(frontend): contribution form refactor [`#3442`](https://github.com/gradido/gradido/pull/3442)
|
||||
- feat(backend): correct user data transfer to gms [`#3433`](https://github.com/gradido/gradido/pull/3433)
|
||||
- feat(backend): switch marker colors of community location and user location [`#3445`](https://github.com/gradido/gradido/pull/3445)
|
||||
- feat(backend): darker email font for content [`#3441`](https://github.com/gradido/gradido/pull/3441)
|
||||
- fix(other): clear also ~/.cache/yarn [`#3362`](https://github.com/gradido/gradido/pull/3362)
|
||||
- refactor(backend): test refactor better bun compatibility [`#3438`](https://github.com/gradido/gradido/pull/3438)
|
||||
- chore(release): v2.4.5 beta [`#3435`](https://github.com/gradido/gradido/pull/3435)
|
||||
- feat(backend): add answer button inside E-Mail [`#3431`](https://github.com/gradido/gradido/pull/3431)
|
||||
- feat(other): build config in deployment [`#3430`](https://github.com/gradido/gradido/pull/3430)
|
||||
- fix(dht): and federation using config in bare-metal setup [`#3434`](https://github.com/gradido/gradido/pull/3434)
|
||||
|
||||
@ -3,4 +3,5 @@ GRAPHQL_PATH=/graphql
|
||||
WALLET_URL=http://localhost
|
||||
WALLET_AUTH_PATH=/authenticate?token=
|
||||
WALLET_LOGIN_PATH=/login
|
||||
DEBUG_DISABLE_AUTH=false
|
||||
DEBUG_DISABLE_AUTH=false
|
||||
HUMHUB_ACTIVE=false
|
||||
@ -6,3 +6,6 @@ WALLET_AUTH_PATH=$WALLET_AUTH_PATH
|
||||
WALLET_LOGIN_PATH=$WALLET_LOGIN_PATH
|
||||
GRAPHQL_PATH=$GRAPHQL_PATH
|
||||
DEBUG_DISABLE_AUTH=false
|
||||
|
||||
HUMHUB_ACTIVE=$HUMHUB_ACTIVE
|
||||
HUMHUB_API_URL=$HUMHUB_API_URL
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administration Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "2.4.5",
|
||||
"version": "2.5.1",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"start": "node run/server.js",
|
||||
@ -55,7 +55,8 @@
|
||||
"vue-router": "4.4.0",
|
||||
"vue3-datepicker": "^0.4.0",
|
||||
"vuex": "4.1.0",
|
||||
"vuex-persistedstate": "4.1.0"
|
||||
"vuex-persistedstate": "4.1.0",
|
||||
"yup": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apollo/client": "^3.10.8",
|
||||
@ -88,6 +89,7 @@
|
||||
"unplugin-icons": "^0.19.0",
|
||||
"unplugin-vue-components": "^0.27.3",
|
||||
"vite-plugin-environment": "^1.1.3",
|
||||
"vite-plugin-graphql-loader": "^4.0.4",
|
||||
"vitest": "^2.0.5",
|
||||
"vitest-canvas-mock": "^0.3.3"
|
||||
},
|
||||
|
||||
17
admin/src/components/CollapseIcon.vue
Normal file
17
admin/src/components/CollapseIcon.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="collapse-icon">
|
||||
<IBiArrowUpCircle v-if="visible" class="text-black h2" />
|
||||
<IBiArrowDownCircle v-else class="text-muted h2" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'CollapseIcon',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -222,6 +222,10 @@ const onSubmit = () => {
|
||||
}
|
||||
}
|
||||
toastSuccess(t('message.request'))
|
||||
form.value = {
|
||||
text: '',
|
||||
memo: props.contributionMemo,
|
||||
}
|
||||
loading.value = false
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@ -93,10 +93,12 @@ describe('ContributionMessagesList', () => {
|
||||
|
||||
wrapper = mount(ContributionMessagesList, {
|
||||
props: {
|
||||
contributionId: 42,
|
||||
contributionMemo: 'test memo',
|
||||
contributionUserId: 108,
|
||||
contributionStatus: 'PENDING',
|
||||
contribution: {
|
||||
id: 42,
|
||||
memo: 'test memo',
|
||||
userId: 108,
|
||||
status: 'PENDING',
|
||||
},
|
||||
hideResubmission: true,
|
||||
},
|
||||
global: {
|
||||
@ -137,7 +139,7 @@ describe('ContributionMessagesList', () => {
|
||||
})
|
||||
|
||||
it('does not render the ContributionMessagesFormular when status is not PENDING or IN_PROGRESS', async () => {
|
||||
await wrapper.setProps({ contributionStatus: 'COMPLETED' })
|
||||
await wrapper.setProps({ contribution: { status: 'COMPLETED' } })
|
||||
expect(wrapper.find('contribution-messages-formular-stub').exists()).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
@ -1,17 +1,49 @@
|
||||
<template>
|
||||
<div class="contribution-messages-list">
|
||||
<BListGroup>
|
||||
<BListGroupItem>
|
||||
<routerLink :to="searchLink" :title="$t('goTo.userSearch')">
|
||||
{{ contribution.firstName }} {{ contribution.lastName }}
|
||||
</routerLink>
|
||||
|
||||
<a :href="mailtoLink">{{ contribution.email }}</a>
|
||||
<IBiFilter id="filter-by-email" class="ms-1 pointer" @click="searchForEmail" />
|
||||
<BTooltip target="filter-by-email" triggers="hover">
|
||||
{{ $t('filter.byEmail') }}
|
||||
</BTooltip>
|
||||
|
||||
{{ contribution.username }}
|
||||
|
||||
<span>
|
||||
<a
|
||||
v-if="humhubProfileLink"
|
||||
id="humhub-username"
|
||||
:href="humhubProfileLink"
|
||||
target="_blank"
|
||||
>
|
||||
<i-arcticons-circles class="svg-icon" />
|
||||
</a>
|
||||
<BTooltip target="humhub-username" triggers="hover">
|
||||
{{ $t('goTo.humhubProfile') }}
|
||||
</BTooltip>
|
||||
</span>
|
||||
</BListGroupItem>
|
||||
<BListGroupItem>
|
||||
{{ $t('registered') }}: {{ new Date(contribution.createdAt).toLocaleString() }}
|
||||
</BListGroupItem>
|
||||
</BListGroup>
|
||||
<BContainer>
|
||||
<div v-for="message in messages" :key="message.id">
|
||||
<contribution-messages-list-item
|
||||
:message="message"
|
||||
:contribution-user-id="contributionUserId"
|
||||
:contribution-user-id="contribution.userId"
|
||||
/>
|
||||
</div>
|
||||
</BContainer>
|
||||
<div v-if="contributionStatus === 'PENDING' || contributionStatus === 'IN_PROGRESS'">
|
||||
<div v-if="contribution.status === 'PENDING' || contribution.status === 'IN_PROGRESS'">
|
||||
<contribution-messages-formular
|
||||
:contribution-id="contributionId"
|
||||
:contribution-memo="contributionMemo"
|
||||
:contribution-id="contribution.id"
|
||||
:contribution-memo="contribution.memo"
|
||||
:hide-resubmission="hideResubmission"
|
||||
:input-resubmission-date="resubmissionAt"
|
||||
@get-list-contribution-messages="refetch"
|
||||
@ -24,27 +56,16 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
|
||||
import { adminListContributionMessages } from '../../graphql/adminListContributionMessages.js'
|
||||
import { useAppToast } from '@/composables/useToast'
|
||||
import { BListGroupItem } from 'bootstrap-vue-next'
|
||||
import CONFIG from '@/config'
|
||||
|
||||
const props = defineProps({
|
||||
contributionId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
contributionMemo: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
contributionStatus: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
contributionUserId: {
|
||||
type: Number,
|
||||
contribution: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
hideResubmission: {
|
||||
@ -57,15 +78,36 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update-status', 'reload-contribution', 'update-contributions'])
|
||||
const emit = defineEmits([
|
||||
'update-status',
|
||||
'reload-contribution',
|
||||
'update-contributions',
|
||||
'search-for-email',
|
||||
])
|
||||
const { toastError } = useAppToast()
|
||||
const mailtoLink = computed(() => {
|
||||
return `mailto:${props.contribution.email}`
|
||||
})
|
||||
const searchLink = computed(() => {
|
||||
return `/user?search=${props.contribution.email}`
|
||||
})
|
||||
const humhubProfileLink = computed(() => {
|
||||
if (CONFIG.HUMHUB_ACTIVE !== true) {
|
||||
return undefined
|
||||
}
|
||||
let url = CONFIG.HUMHUB_API_URL
|
||||
if (url.endsWith('/')) {
|
||||
url = url.slice(0, -1)
|
||||
}
|
||||
return `${url}/u/${props.contribution.humhubUsername}`
|
||||
})
|
||||
|
||||
const messages = ref([])
|
||||
|
||||
const { onResult, onError, result, refetch } = useQuery(
|
||||
adminListContributionMessages,
|
||||
{
|
||||
contributionId: props.contributionId,
|
||||
contributionId: props.contribution.id,
|
||||
},
|
||||
{
|
||||
fetchPolicy: 'no-cache',
|
||||
@ -91,6 +133,10 @@ const reloadContribution = (id) => {
|
||||
const updateContributions = () => {
|
||||
emit('update-contributions')
|
||||
}
|
||||
|
||||
const searchForEmail = () => {
|
||||
emit('search-for-email', props.contribution.email)
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.temp-message {
|
||||
|
||||
@ -56,7 +56,7 @@ export default {
|
||||
? formatDistanceToNow(new Date(dateString), {
|
||||
includeSecond: true,
|
||||
addSuffix: true,
|
||||
locale: useDateLocale,
|
||||
locale: useDateLocale(),
|
||||
})
|
||||
: ''
|
||||
},
|
||||
|
||||
@ -101,12 +101,13 @@ describe('NavBar', () => {
|
||||
describe('Navbar Menu', () => {
|
||||
it('has correct menu items', () => {
|
||||
const navItems = wrapper.findAll('.nav-item a')
|
||||
expect(navItems).toHaveLength(7)
|
||||
expect(navItems).toHaveLength(8)
|
||||
expect(navItems[0].attributes('href')).toBe('/user')
|
||||
expect(navItems[1].attributes('href')).toBe('/creation-confirm')
|
||||
expect(navItems[2].attributes('href')).toBe('/contribution-links')
|
||||
expect(navItems[3].attributes('href')).toBe('/federation')
|
||||
expect(navItems[4].attributes('href')).toBe('/statistic')
|
||||
expect(navItems[4].attributes('href')).toBe('/projectBranding')
|
||||
expect(navItems[5].attributes('href')).toBe('/statistic')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -32,10 +32,24 @@
|
||||
<BNavItem to="/federation" :active="isActive('federation')">
|
||||
{{ $t('navbar.instances') }}
|
||||
</BNavItem>
|
||||
<BNavItem
|
||||
to="/projectBranding"
|
||||
:active="isActive('projectBranding')"
|
||||
:title="$t('navbar.projectBrandingTooltip')"
|
||||
>
|
||||
{{ $t('navbar.projectBranding') }}
|
||||
</BNavItem>
|
||||
<BNavItem to="/statistic" :active="isActive('statistic')">
|
||||
{{ $t('navbar.statistic') }}
|
||||
</BNavItem>
|
||||
<BNavItem @click="handleWallet">{{ $t('navbar.my-account') }}</BNavItem>
|
||||
<BLink
|
||||
href="https://gradido.net/coin/moderators-tutorial/"
|
||||
class="nav-link"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t('help.help') }}
|
||||
</BLink>
|
||||
<BNavItem @click="handleLogout">{{ $t('navbar.logout') }}</BNavItem>
|
||||
</BNavbarNav>
|
||||
</BCollapse>
|
||||
@ -58,6 +72,7 @@ import {
|
||||
BNavbarToggle,
|
||||
vBToggle,
|
||||
vBColorMode,
|
||||
BLink,
|
||||
} from 'bootstrap-vue-next'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
|
||||
86
admin/src/components/ProjectBranding/ListHumhubSpaces.vue
Normal file
86
admin/src/components/ProjectBranding/ListHumhubSpaces.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="list-unstyled list-group mb-3">
|
||||
<li
|
||||
v-for="space in spaces"
|
||||
:key="space.id"
|
||||
:title="space.description"
|
||||
:class="[
|
||||
'list-group-item',
|
||||
'list-group-item-action',
|
||||
'd-flex',
|
||||
'justify-content-between',
|
||||
'align-items-center',
|
||||
'cursor-pointer',
|
||||
{ active: space.id === selectedSpaceId },
|
||||
]"
|
||||
@click="chooseSpace(space)"
|
||||
>
|
||||
<div>
|
||||
<input v-model="selectedSpaceId" type="radio" :value="space.id" class="me-2" />
|
||||
{{ space.name }}
|
||||
</div>
|
||||
<a :href="space.url" target="_blank" @click.stop>
|
||||
{{ $t('projectBranding.openSpaceInHumhub') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<b-pagination
|
||||
v-if="result && paginationTotal > ITEMS_PER_PAGE"
|
||||
v-model="paginationPage"
|
||||
:total-rows="paginationTotal"
|
||||
:per-page="ITEMS_PER_PAGE"
|
||||
aria-controls="list-humhub-spaces"
|
||||
@update:model-value="refetch({ page: $event })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { spaces as spacesQuery } from '@/graphql/projectBranding.graphql'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['chooseSpace'])
|
||||
function chooseSpace(space) {
|
||||
selectedSpaceId.value = space.id
|
||||
emit('chooseSpace', space)
|
||||
}
|
||||
|
||||
const ITEMS_PER_PAGE = 20
|
||||
const page = ref(1)
|
||||
const selectedSpaceId = ref(props.modelValue)
|
||||
const { result, refetch } = useQuery(spacesQuery, { page: page.value, limit: ITEMS_PER_PAGE })
|
||||
|
||||
const spaces = computed(() => result.value?.spaces?.results || [])
|
||||
const paginationTotal = computed(() => result.value?.spaces?.pagination?.total || 0)
|
||||
const paginationPage = computed(() => result.value?.spaces?.pagination?.page || 1)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.modelValue) {
|
||||
if (!spaces.value.some((space) => space.id === props.modelValue)) {
|
||||
const targetPage = Math.ceil(props.modelValue / ITEMS_PER_PAGE)
|
||||
page.value = targetPage
|
||||
refetch({ page: targetPage })
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.list-group-item-action:hover:not(.active) {
|
||||
background-color: #ececec;
|
||||
color: #0056b3;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.list-group-item-action.active > a {
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
171
admin/src/components/ProjectBranding/ProjectBrandingForm.vue
Normal file
171
admin/src/components/ProjectBranding/ProjectBrandingForm.vue
Normal file
@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="project-branding-form">
|
||||
<BForm @submit.prevent="submit">
|
||||
<ValidatedInput
|
||||
:model-value="name"
|
||||
name="name"
|
||||
:label="$t('name')"
|
||||
:rules="validationSchema.fields.name"
|
||||
class="mb-3"
|
||||
@update:model-value="updateField"
|
||||
/>
|
||||
<ValidatedInput
|
||||
:model-value="alias"
|
||||
name="alias"
|
||||
:label="$t('alias')"
|
||||
:rules="validationSchema.fields.alias"
|
||||
class="mb-3"
|
||||
@update:model-value="updateField"
|
||||
/>
|
||||
<ValidatedInput
|
||||
:model-value="description"
|
||||
name="description"
|
||||
:label="$t('description')"
|
||||
:rules="validationSchema.fields.description"
|
||||
textarea="true"
|
||||
class="mb-3"
|
||||
@update:model-value="updateField"
|
||||
/>
|
||||
<BButton
|
||||
variant="outline-secondary"
|
||||
class="mb-3"
|
||||
:title="result?.space.description"
|
||||
@click="isModalVisible = true"
|
||||
>
|
||||
{{ selectedSpaceText }}
|
||||
</BButton>
|
||||
<BFormGroup
|
||||
:label="$t('projectBranding.newUserToSpace')"
|
||||
label-for="newUserToSpace-input-field"
|
||||
class="mb-3"
|
||||
>
|
||||
<BFormCheckbox
|
||||
id="newUserToSpace-input-field"
|
||||
:model-value="newUserToSpace"
|
||||
name="newUserToSpace"
|
||||
value="true"
|
||||
unchecked-value="false"
|
||||
@update:model-value="(value) => updateField(value, 'newUserToSpace')"
|
||||
>
|
||||
{{ $t('projectBranding.newUserToSpaceTooltip') }}
|
||||
</BFormCheckbox>
|
||||
</BFormGroup>
|
||||
<ValidatedInput
|
||||
:model-value="logoUrl"
|
||||
name="logoUrl"
|
||||
:label="$t('logo')"
|
||||
:rules="validationSchema.fields.logoUrl"
|
||||
class="mb-3"
|
||||
@update:model-value="updateField"
|
||||
/>
|
||||
<BFormInvalidFeedback v-if="errorMessage" class="d-block mb-3">
|
||||
{{ errorMessage }}
|
||||
</BFormInvalidFeedback>
|
||||
<div class="d-flex gap-2">
|
||||
<BButton type="submit" variant="primary">{{ $t('save') }}</BButton>
|
||||
<BButton type="reset" variant="secondary" @click="resetForm">{{ $t('reset') }}</BButton>
|
||||
</div>
|
||||
</BForm>
|
||||
<BModal v-model="isModalVisible" title="Select Space" hide-footer>
|
||||
<ListHumhubSpaces :model-value="spaceId" @choose-space="chooseSpace" />
|
||||
</BModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ValidatedInput from '@/components/input/ValidatedInput'
|
||||
import ListHumhubSpaces from '@/components/ProjectBranding/ListHumhubSpaces.vue'
|
||||
import { spaceWithNameAndDescription } from '@/graphql/projectBranding.graphql'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { reactive, computed, watch, ref } from 'vue'
|
||||
import { object, string, boolean, number } from 'yup'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, required: true },
|
||||
})
|
||||
|
||||
const form = reactive({ ...props.modelValue })
|
||||
const isModalVisible = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const { t } = useI18n()
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => Object.assign(form, newValue),
|
||||
)
|
||||
const name = computed(() => form.name)
|
||||
const alias = computed(() => form.alias)
|
||||
const description = computed(() => form.description)
|
||||
const spaceId = computed(() => form.spaceId)
|
||||
const newUserToSpace = computed(() => form.newUserToSpace)
|
||||
const logoUrl = computed(() => form.logoUrl)
|
||||
|
||||
// show space
|
||||
const { result } = useQuery(spaceWithNameAndDescription, () => ({ id: spaceId.value }), {
|
||||
enabled: !!spaceId.value,
|
||||
})
|
||||
|
||||
const selectedSpaceText = computed(() => {
|
||||
if (!spaceId.value) {
|
||||
return t('projectBranding.selectSpace')
|
||||
}
|
||||
if (!result.value?.space) {
|
||||
return t('projectBranding.noAccessRightSpace', { spaceId: spaceId.value })
|
||||
}
|
||||
return t('projectBranding.chosenSpace', { space: result.value.space.name })
|
||||
})
|
||||
|
||||
const validationSchema = object({
|
||||
name: string().min(3).max(255).required(),
|
||||
alias: string()
|
||||
.matches(/^[a-z0-9-_]+$/, {
|
||||
message: 'Alias can only contain lowercase letters, numbers, hyphens, and underscores.',
|
||||
})
|
||||
.min(3)
|
||||
.max(32)
|
||||
.required(),
|
||||
description: string().nullable().optional(),
|
||||
spaceId: number().nullable().optional(),
|
||||
spaceUrl: string().url('Space URL must be a valid URL.').max(255).nullable().optional(),
|
||||
newUserToSpace: boolean().optional(),
|
||||
logoUrl: string().url('Logo URL must be a valid URL.').max(255).nullable().optional(),
|
||||
})
|
||||
|
||||
function chooseSpace(value) {
|
||||
updateField(value.id, 'spaceId')
|
||||
updateField(value.url, 'spaceUrl')
|
||||
}
|
||||
|
||||
function updateField(value, name) {
|
||||
form[name] = value
|
||||
}
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
function submit() {
|
||||
validationSchema
|
||||
.validate(form, { stripUnknown: true })
|
||||
.then((cleanedForm) => {
|
||||
emit('update:modelValue', { ...cleanedForm, id: props.modelValue.id })
|
||||
})
|
||||
.catch((err) => {
|
||||
errorMessage.value = err.message
|
||||
})
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
if (props.modelValue.id === undefined) {
|
||||
Object.assign(form, {
|
||||
name: '',
|
||||
alias: '',
|
||||
description: undefined,
|
||||
spaceId: undefined,
|
||||
spaceUrl: undefined,
|
||||
newUserToSpace: false,
|
||||
logoUrl: undefined,
|
||||
})
|
||||
return
|
||||
} else {
|
||||
Object.assign(form, props.modelValue)
|
||||
}
|
||||
errorMessage.value = ''
|
||||
}
|
||||
</script>
|
||||
117
admin/src/components/ProjectBranding/ProjectBrandingItem.vue
Normal file
117
admin/src/components/ProjectBranding/ProjectBrandingItem.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="project-branding-item">
|
||||
<BRow :title="item.description" @click="toggleDetails">
|
||||
<BCol cols="3">
|
||||
{{ item.name }}
|
||||
<br />
|
||||
{{ frontendLoginUrl }}
|
||||
<BButton
|
||||
v-if="frontendLoginUrl"
|
||||
v-b-tooltip.hover
|
||||
variant="secondary"
|
||||
:title="$t('copy-to-clipboard')"
|
||||
@click.stop="copyToClipboard(frontendLoginUrl)"
|
||||
>
|
||||
<i class="fas fa-copy"></i>
|
||||
</BButton>
|
||||
</BCol>
|
||||
<BCol cols="2">{{ item.alias }}</BCol>
|
||||
<BCol cols="2">
|
||||
<span v-if="item.newUserToSpace" class="text-success">
|
||||
<i class="fas fa-check"></i>
|
||||
</span>
|
||||
<span v-else class="text-danger">
|
||||
<i class="fas fa-times"></i>
|
||||
</span>
|
||||
</BCol>
|
||||
<BCol cols="3" class="me-2">
|
||||
<img class="img-fluid" :src="item.logoUrl" :alt="item.logoUrl" />
|
||||
</BCol>
|
||||
<BCol v-if="store.state.moderator.roles.includes('ADMIN')" cols="1">
|
||||
<BButton v-b-tooltip.hover variant="danger" :title="$t('delete')" @click.stop="deleteItem">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</BButton>
|
||||
</BCol>
|
||||
</BRow>
|
||||
<BRow v-if="details || item.id === undefined" class="details">
|
||||
<BCol colspan="5">
|
||||
<BCard>
|
||||
<ProjectBrandingForm :model-value="item" @update:model-value="update" />
|
||||
</BCard>
|
||||
</BCol>
|
||||
</BRow>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, toRefs } from 'vue'
|
||||
import ProjectBrandingForm from './ProjectBrandingForm.vue'
|
||||
import { deleteProjectBranding, upsertProjectBranding } from '@/graphql/projectBranding.graphql'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from 'vuex'
|
||||
import { useMutation } from '@vue/apollo-composable'
|
||||
import CONFIG from '@/config'
|
||||
import { useAppToast } from '@/composables/useToast'
|
||||
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
const { toastSuccess, toastError } = useAppToast()
|
||||
|
||||
const props = defineProps({
|
||||
item: { type: Object, required: true },
|
||||
})
|
||||
const { item } = toRefs(props)
|
||||
const details = ref(false)
|
||||
|
||||
const emit = defineEmits(['update:item', 'deleted:item'])
|
||||
const frontendLoginUrl = computed(() => {
|
||||
if (item.value.alias && item.value.alias.length > 0) {
|
||||
return `${CONFIG.WALLET_LOGIN_URL}?project=${item.value.alias}`
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
async function copyToClipboard(text) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
toastSuccess(t('copied-to-clipboard'))
|
||||
} catch (err) {
|
||||
toastError(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDetails() {
|
||||
if (store.state.moderator.roles.includes('ADMIN')) {
|
||||
details.value = !details.value
|
||||
}
|
||||
}
|
||||
|
||||
function update(form) {
|
||||
const { mutate } = useMutation(upsertProjectBranding)
|
||||
|
||||
mutate({
|
||||
input: { ...form },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
emit('update:item', data.upsertProjectBranding)
|
||||
if (form.id) {
|
||||
toastSuccess(t('projectBranding.updated'))
|
||||
} else {
|
||||
toastSuccess(t('projectBranding.created'))
|
||||
}
|
||||
details.value = false
|
||||
})
|
||||
.catch((error) => {
|
||||
toastError(t('projectBranding.error', { message: error.message }))
|
||||
})
|
||||
}
|
||||
function deleteItem() {
|
||||
const { mutate } = useMutation(deleteProjectBranding)
|
||||
|
||||
mutate({
|
||||
id: item.value.id,
|
||||
}).then(() => {
|
||||
emit('deleted:item', item.value.id)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@ -67,6 +67,7 @@
|
||||
<BButton v-if="row.item.messagesCount > 0" @click="rowToggleDetails(row, 0)">
|
||||
<IBiChatDots />
|
||||
</BButton>
|
||||
<collapse-icon v-else :visible="row.detailsShowing" @click="rowToggleDetails(row, 0)" />
|
||||
</template>
|
||||
<template #cell(deny)="row">
|
||||
<div v-if="!myself(row.item)">
|
||||
@ -92,6 +93,12 @@
|
||||
</BButton>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(firstName)="row">
|
||||
<div class="no-select">{{ row.item.firstName }}</div>
|
||||
</template>
|
||||
<template #cell(lastName)="row">
|
||||
<div class="no-select">{{ row.item.lastName }}</div>
|
||||
</template>
|
||||
<template #row-details="row">
|
||||
<row-details
|
||||
:row="row"
|
||||
@ -103,6 +110,7 @@
|
||||
<template #show-creation>
|
||||
<div v-if="row.item.moderatorId">
|
||||
<edit-creation-formular
|
||||
v-if="row.item.confirmedAt === null"
|
||||
type="singleCreation"
|
||||
:item="row.item"
|
||||
:row="row"
|
||||
@ -112,15 +120,13 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<contribution-messages-list
|
||||
:contribution-id="row.item.id"
|
||||
:contribution-status="row.item.status"
|
||||
:contribution-user-id="row.item.userId"
|
||||
:contribution-memo="row.item.memo"
|
||||
:contribution="row.item"
|
||||
:resubmission-at="row.item.resubmissionAt"
|
||||
:hide-resubmission="hideResubmission"
|
||||
@update-status="updateStatus"
|
||||
@reload-contribution="reloadContribution"
|
||||
@update-contributions="updateContributions"
|
||||
@search-for-email="$emit('search-for-email', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -168,7 +174,13 @@ export default {
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
emits: ['update-contributions', 'reload-contribution', 'update-status', 'show-overlay'],
|
||||
emits: [
|
||||
'update-contributions',
|
||||
'reload-contribution',
|
||||
'update-status',
|
||||
'show-overlay',
|
||||
'search-for-email',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
slotIndex: 0,
|
||||
@ -176,6 +188,12 @@ export default {
|
||||
creationUserData: {},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.addClipboardListener()
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.removeClipboardListener()
|
||||
},
|
||||
methods: {
|
||||
myself(item) {
|
||||
return item.userId === this.$store.state.moderator.id
|
||||
@ -201,28 +219,39 @@ export default {
|
||||
this.$emit('update-contributions')
|
||||
},
|
||||
rowToggleDetails(row, index) {
|
||||
if (this.openRow) {
|
||||
if (this.openRow.index === row.index) {
|
||||
if (index === this.slotIndex) {
|
||||
row.toggleDetails()
|
||||
this.openRow = null
|
||||
} else {
|
||||
this.slotIndex = index
|
||||
}
|
||||
} else {
|
||||
this.openRow.toggleDetails()
|
||||
row.toggleDetails()
|
||||
this.slotIndex = index
|
||||
this.openRow = row
|
||||
this.creationUserData = row.item
|
||||
}
|
||||
const isSameRow = this.openRow && this.openRow.index === row.index
|
||||
const isSameSlot = index === this.slotIndex
|
||||
|
||||
if (isSameRow && isSameSlot) {
|
||||
row.toggleDetails()
|
||||
this.openRow = null
|
||||
} else {
|
||||
if (this.openRow) {
|
||||
this.openRow.toggleDetails()
|
||||
}
|
||||
row.toggleDetails()
|
||||
this.slotIndex = index
|
||||
this.openRow = row
|
||||
this.creationUserData = row.item
|
||||
}
|
||||
},
|
||||
addClipboardListener() {
|
||||
document.addEventListener('copy', this.handleCopy)
|
||||
},
|
||||
removeClipboardListener() {
|
||||
document.removeEventListener('copy', this.handleCopy)
|
||||
},
|
||||
handleCopy(event) {
|
||||
// get from user selected text
|
||||
const selectedText = window.getSelection().toString()
|
||||
|
||||
if (selectedText) {
|
||||
// remove hashtags
|
||||
const cleanedText = selectedText.replace(/#[a-zA-Z0-9_-]*/g, '')
|
||||
event.clipboardData.setData('text/plain', cleanedText)
|
||||
event.preventDefault()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -231,4 +260,8 @@ export default {
|
||||
background-color: #e1a908;
|
||||
border-color: #e1a908;
|
||||
}
|
||||
|
||||
.no-select {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
45
admin/src/components/input/LabeledInput.vue
Normal file
45
admin/src/components/input/LabeledInput.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div :class="wrapperClassName">
|
||||
<BFormGroup :label="label" :label-for="labelFor">
|
||||
<BFormTextarea
|
||||
v-if="textarea"
|
||||
v-bind="{ ...$attrs, id: labelFor, name }"
|
||||
v-model="model"
|
||||
trim
|
||||
:rows="4"
|
||||
:max-rows="4"
|
||||
no-resize
|
||||
/>
|
||||
<BFormInput v-else v-bind="{ ...$attrs, id: labelFor, name }" v-model="model" />
|
||||
<slot></slot>
|
||||
</BFormGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, defineOptions, defineModel } from 'vue'
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
textarea: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const model = defineModel()
|
||||
|
||||
const wrapperClassName = computed(() => (props.name ? `input-${props.name}` : 'input'))
|
||||
const labelFor = computed(() => `${props.name}-input-field`)
|
||||
</script>
|
||||
86
admin/src/components/input/ValidatedInput.vue
Normal file
86
admin/src/components/input/ValidatedInput.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<LabeledInput
|
||||
v-bind="$attrs"
|
||||
:min="minValue"
|
||||
:max="maxValue"
|
||||
:model-value="model"
|
||||
:reset-value="resetValue"
|
||||
:locale="$i18n.locale"
|
||||
:required="!isOptional"
|
||||
:label="label"
|
||||
:name="name"
|
||||
:state="valid"
|
||||
@update:model-value="updateValue"
|
||||
>
|
||||
<BFormInvalidFeedback v-if="errorMessage">
|
||||
{{ errorMessage }}
|
||||
</BFormInvalidFeedback>
|
||||
</LabeledInput>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import LabeledInput from './LabeledInput'
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
modelValue: [String, Number, Date],
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
rules: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const model = ref(props.modelValue)
|
||||
|
||||
const valid = computed(() => {
|
||||
if (
|
||||
(props.modelValue === undefined || props.modelValue === '' || props.modelValue === null) &&
|
||||
isOptional.value
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return props.rules.isValidSync(props.modelValue)
|
||||
})
|
||||
const errorMessage = computed(() => {
|
||||
if (props.modelValue === undefined || props.modelValue === '' || props.modelValue === null) {
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
props.rules.validateSync(props.modelValue)
|
||||
return undefined
|
||||
} catch (e) {
|
||||
return e.message
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const updateValue = (newValue) => {
|
||||
emit('update:modelValue', newValue, props.name, valid.value)
|
||||
}
|
||||
|
||||
// update model and if value changed and model isn't null, check validation,
|
||||
// for loading Input with existing value and show correct validation state
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
model.value = props.modelValue
|
||||
},
|
||||
)
|
||||
|
||||
// extract additional parameter like min and max from schema
|
||||
const schemaDescription = computed(() => props.rules.describe())
|
||||
const getTestParameter = (name) =>
|
||||
schemaDescription.value?.tests?.find((t) => t.name === name)?.params[name]
|
||||
const minValue = computed(() => getTestParameter('min'))
|
||||
const maxValue = computed(() => getTestParameter('max'))
|
||||
const resetValue = computed(() => schemaDescription.value.default)
|
||||
const isOptional = computed(() => schemaDescription.value.optional)
|
||||
</script>
|
||||
@ -6,7 +6,7 @@ const pkg = require('../../package')
|
||||
|
||||
const version = {
|
||||
ADMIN_MODULE_PROTOCOL: process.env.ADMIN_MODULE_PROTOCOL ?? 'http',
|
||||
ADMIN_MODULE_HOST: process.env.ADMIN_MODULE_HOST ?? 'localhost',
|
||||
ADMIN_MODULE_HOST: process.env.ADMIN_MODULE_HOST ?? '0.0.0.0',
|
||||
ADMIN_MODULE_PORT: process.env.ADMIN_MODULE_PORT ?? '8080',
|
||||
APP_VERSION: pkg.version,
|
||||
BUILD_COMMIT: process.env.BUILD_COMMIT ?? undefined,
|
||||
@ -50,12 +50,17 @@ const endpoints = {
|
||||
const debug = {
|
||||
DEBUG_DISABLE_AUTH: process.env.DEBUG_DISABLE_AUTH === 'true' ?? false,
|
||||
}
|
||||
const humhub = {
|
||||
HUMHUB_ACTIVE: process.env.HUMHUB_ACTIVE === 'true' || false,
|
||||
HUMHUB_API_URL: process.env.HUMHUB_API_URL ?? COMMUNITY_URL + '/community/',
|
||||
}
|
||||
|
||||
const CONFIG = {
|
||||
...version,
|
||||
...environment,
|
||||
...endpoints,
|
||||
...debug,
|
||||
...humhub,
|
||||
ADMIN_MODULE_URL,
|
||||
COMMUNITY_URL,
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ const {
|
||||
COMMUNITY_URL,
|
||||
DEBUG,
|
||||
GRAPHQL_URI,
|
||||
HUMHUB_ACTIVE,
|
||||
HUMHUB_API_URL,
|
||||
NODE_ENV,
|
||||
PRODUCTION,
|
||||
} = require('gradido-config/build/src/commonSchema.js')
|
||||
@ -17,6 +19,8 @@ module.exports = Joi.object({
|
||||
COMMUNITY_URL,
|
||||
DEBUG,
|
||||
GRAPHQL_URI,
|
||||
HUMHUB_ACTIVE,
|
||||
HUMHUB_API_URL,
|
||||
NODE_ENV,
|
||||
PRODUCTION,
|
||||
|
||||
|
||||
@ -26,6 +26,9 @@ export const adminListContributions = gql`
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
email
|
||||
username
|
||||
humhubUsername
|
||||
amount
|
||||
memo
|
||||
createdAt
|
||||
|
||||
23
admin/src/graphql/fragments.graphql
Normal file
23
admin/src/graphql/fragments.graphql
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
fragment SpaceFields on Space {
|
||||
id
|
||||
name
|
||||
description
|
||||
url
|
||||
}
|
||||
|
||||
fragment PaginationFields on Pagination {
|
||||
total
|
||||
page
|
||||
pages
|
||||
}
|
||||
|
||||
fragment ProjectBrandingCommonFields on ProjectBranding {
|
||||
id
|
||||
name
|
||||
alias
|
||||
description
|
||||
spaceId
|
||||
newUserToSpace
|
||||
logoUrl
|
||||
}
|
||||
35
admin/src/graphql/projectBranding.graphql
Normal file
35
admin/src/graphql/projectBranding.graphql
Normal file
@ -0,0 +1,35 @@
|
||||
#import './fragments.graphql'
|
||||
|
||||
query projectBrandings {
|
||||
projectBrandings {
|
||||
...ProjectBrandingCommonFields
|
||||
}
|
||||
}
|
||||
|
||||
mutation upsertProjectBranding($input: ProjectBrandingInput!) {
|
||||
upsertProjectBranding(input: $input) {
|
||||
...ProjectBrandingCommonFields
|
||||
}
|
||||
}
|
||||
|
||||
mutation deleteProjectBranding($id: ID!) {
|
||||
deleteProjectBranding(id: $id)
|
||||
}
|
||||
|
||||
query spaces($page: Int!, $limit: Int!) {
|
||||
spaces(page: $page, limit: $limit) {
|
||||
pagination {
|
||||
...PaginationFields
|
||||
}
|
||||
results {
|
||||
...SpaceFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query spaceWithNameAndDescription($id: ID!) {
|
||||
space(id: $id) {
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
{
|
||||
"GDD": "GDD",
|
||||
"actions": "Aktionen",
|
||||
"alias": "Alias",
|
||||
"all_emails": "Alle Nutzer",
|
||||
"back": "zurück",
|
||||
"change_user_role": "Nutzerrolle ändern",
|
||||
@ -33,7 +35,8 @@
|
||||
"validTo": "Enddatum"
|
||||
},
|
||||
"contributionMessagesForm": {
|
||||
"resubmissionDateInPast": "Wiedervorlage Datum befindet sich in der Vergangenheit!"
|
||||
"resubmissionDateInPast": "Wiedervorlage Datum befindet sich in der Vergangenheit!",
|
||||
"hasRegisteredAt": "hat sich am {createdAt} registriert."
|
||||
},
|
||||
"contributions": {
|
||||
"all": "Alle",
|
||||
@ -42,6 +45,8 @@
|
||||
"denied": "Abgelehnt",
|
||||
"open": "Offen"
|
||||
},
|
||||
"copied-to-clipboard": "In die Zwischenablage kopiert",
|
||||
"copy-to-clipboard": "In die Zwischenablage kopieren",
|
||||
"created": "Geschöpft",
|
||||
"createdAt": "Angelegt",
|
||||
"creation": "Schöpfung",
|
||||
@ -68,6 +73,8 @@
|
||||
"deleted": "gelöscht",
|
||||
"deleted_user": "Alle gelöschten Nutzer",
|
||||
"deny": "Ablehnen",
|
||||
"description": "Beschreibung",
|
||||
"details": "Details",
|
||||
"e_mail": "E-Mail",
|
||||
"edit": "bearbeiten",
|
||||
"enabled": "aktiviert",
|
||||
@ -92,6 +99,9 @@
|
||||
"verified": "Verifiziert",
|
||||
"verifiedAt": "Verifiziert am"
|
||||
},
|
||||
"filter": {
|
||||
"byEmail": "Nach E-Mail filtern"
|
||||
},
|
||||
"firstname": "Vorname",
|
||||
"footer": {
|
||||
"app_version": "App version {version}",
|
||||
@ -112,6 +122,10 @@
|
||||
"describe": "Teilt Koordinaten im Format 'Breitengrad, Längengrad' automatisch auf. Fügen sie hier einfach z.B. ihre Koordinaten von Google Maps, zum Beispiel: 49.28187664243721, 9.740672183943639, ein."
|
||||
}
|
||||
},
|
||||
"goTo": {
|
||||
"userSearch": "Zur Nutzersuche gehen",
|
||||
"humhubProfile": "Zum Humhub Profil gehen"
|
||||
},
|
||||
"help": {
|
||||
"help": "Hilfe",
|
||||
"transactionlist": {
|
||||
@ -127,6 +141,8 @@
|
||||
"lastname": "Nachname",
|
||||
"latitude": "Breitengrad:",
|
||||
"latitude-longitude-smart": "Breitengrad, Längengrad",
|
||||
"link": "Link",
|
||||
"logo": "Logo",
|
||||
"longitude": "Längengrad:",
|
||||
"math": {
|
||||
"equals": "=",
|
||||
@ -134,7 +150,7 @@
|
||||
"plus": "+"
|
||||
},
|
||||
"message": {
|
||||
"request": "Die Anfrage wurde gesendet."
|
||||
"request": "Die Eingabe wurde gespeichert."
|
||||
},
|
||||
"moderator": {
|
||||
"history": "Die Daten wurden geändert. Dies sind die alten Daten.",
|
||||
@ -155,6 +171,8 @@
|
||||
"instances": "Instanzen",
|
||||
"logout": "Abmelden",
|
||||
"my-account": "Mein Konto",
|
||||
"projectBranding": "Projekt Branding",
|
||||
"projectBrandingTooltip": "Nutze ein eigenes Logo im Gradido Login und füge neue Benutzer einem Humhub-Space hinzu",
|
||||
"statistic": "Statistik",
|
||||
"user_search": "Nutzersuche"
|
||||
},
|
||||
@ -199,8 +217,24 @@
|
||||
"yes": "Ja, Nutzer wiederherstellen"
|
||||
}
|
||||
},
|
||||
"projectBranding": {
|
||||
"addTooltip": "Neuen Projekt Branding Eintrag hinzufügen",
|
||||
"chosenSpace": "Gewählter Space: {space}",
|
||||
"created": "Neuer Projekt Branding Eintrag wurde erstellt.",
|
||||
"error": "Fehler beim Erstellen des Projekt Branding Eintrags: {message}",
|
||||
"noAccessRightSpace": "Gewählter Space: {spaceId} (Keine Zugriffsrechte)",
|
||||
"openSpaceInHumhub": "In Humhub öffnen",
|
||||
"spaceId": "Humhub Space ID",
|
||||
"selectSpace": "Humhub Space auswählen",
|
||||
"title": "Projekt Brandings",
|
||||
"updated": "Projekt Branding Eintrag wurde aktualisiert.",
|
||||
"newUserToSpace": "Benutzer hinzufügen?",
|
||||
"newUserToSpaceTooltip": "Neue Benutzer automatisch zum Space hinzufügen, falls Space vorhanden"
|
||||
},
|
||||
"redeemed": "eingelöst",
|
||||
"registered": "Registriert",
|
||||
"removeNotSelf": "Als Admin/Moderator kannst du dich nicht selber löschen.",
|
||||
"reset": "Zurücksetzen",
|
||||
"save": "Speichern",
|
||||
"statistic": {
|
||||
"activeUsers": "Aktive Mitglieder",
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
{
|
||||
"GDD": "GDD",
|
||||
"actions": "Actions",
|
||||
"alias": "Alias",
|
||||
"all_emails": "All users",
|
||||
"back": "back",
|
||||
"change_user_role": "Change user role",
|
||||
@ -33,7 +35,8 @@
|
||||
"validTo": "End-Date"
|
||||
},
|
||||
"contributionMessagesForm": {
|
||||
"resubmissionDateInPast": "Resubmission date is in the past!"
|
||||
"resubmissionDateInPast": "Resubmission date is in the past!",
|
||||
"hasRegisteredAt": "registered on {createdAt}."
|
||||
},
|
||||
"contributions": {
|
||||
"all": "All",
|
||||
@ -42,6 +45,8 @@
|
||||
"denied": "Rejected",
|
||||
"open": "Open"
|
||||
},
|
||||
"copied-to-clipboard": "Copied to clipboard",
|
||||
"copy-to-clipboard": "Copy to clipboard",
|
||||
"created": "Created for",
|
||||
"createdAt": "Created at",
|
||||
"creation": "Creation",
|
||||
@ -68,6 +73,8 @@
|
||||
"deleted": "deleted",
|
||||
"deleted_user": "All deleted user",
|
||||
"deny": "Reject",
|
||||
"description": "Description",
|
||||
"details": "Details",
|
||||
"e_mail": "E-mail",
|
||||
"edit": "edit",
|
||||
"enabled": "enabled",
|
||||
@ -92,6 +99,9 @@
|
||||
"verified": "Verified",
|
||||
"verifiedAt": "Verified at"
|
||||
},
|
||||
"filter": {
|
||||
"byEmail": "Filter by email"
|
||||
},
|
||||
"firstname": "Firstname",
|
||||
"footer": {
|
||||
"app_version": "App version {version}",
|
||||
@ -112,6 +122,10 @@
|
||||
"describe": "Automatically splits coordinates in the format 'latitude, longitude'. Simply enter your coordinates from Google Maps here, for example: 49.28187664243721, 9.740672183943639."
|
||||
}
|
||||
},
|
||||
"goTo": {
|
||||
"userSearch": "Go to user search",
|
||||
"humhubProfile": "Go to Humhub profile"
|
||||
},
|
||||
"help": {
|
||||
"help": "Help",
|
||||
"transactionlist": {
|
||||
@ -127,6 +141,8 @@
|
||||
"lastname": "Lastname",
|
||||
"latitude": "Latitude:",
|
||||
"latitude-longitude-smart": "Latitude, Longitude",
|
||||
"link": "Link",
|
||||
"logo": "Logo",
|
||||
"longitude": "Longitude:",
|
||||
"math": {
|
||||
"equals": "=",
|
||||
@ -134,7 +150,7 @@
|
||||
"plus": "+"
|
||||
},
|
||||
"message": {
|
||||
"request": "Request has been sent."
|
||||
"request": "The entry has been saved."
|
||||
},
|
||||
"moderator": {
|
||||
"history": "The data has been changed. This is the old data.",
|
||||
@ -155,6 +171,8 @@
|
||||
"instances": "Instances",
|
||||
"logout": "Logout",
|
||||
"my-account": "My Account",
|
||||
"projectBranding": "Project Branding",
|
||||
"projectBrandingTooltip": "Use your own logo in the Gradido login and add new users to a Humhub space",
|
||||
"statistic": "Statistic",
|
||||
"user_search": "User search"
|
||||
},
|
||||
@ -199,9 +217,25 @@
|
||||
"yes": "Yes,undelete user"
|
||||
}
|
||||
},
|
||||
"projectBranding": {
|
||||
"addTooltip": "Add new project branding entry",
|
||||
"chosenSpace": "Choosen Humhub Space: {space}",
|
||||
"created": "New project branding entry has been created.",
|
||||
"error": "Error when creating the project branding entry: {message}",
|
||||
"noAccessRightSpace": "Selected space: {spaceId} (No access rights)",
|
||||
"openSpaceInHumhub": "Open in Humhub",
|
||||
"spaceId": "Humhub Space ID",
|
||||
"selectSpace": "Select Humhub Space",
|
||||
"title": "Project Branding",
|
||||
"updated": "Project branding entry has been updated.",
|
||||
"newUserToSpace": "Add user?",
|
||||
"newUserToSpaceTooltip": "The hours should contain a maximum of two decimal places"
|
||||
},
|
||||
"redeemed": "redeemed",
|
||||
"registered": "Registered",
|
||||
"removeNotSelf": "As an admin/moderator, you cannot delete yourself.",
|
||||
"save": "Speichern",
|
||||
"reset": "Reset",
|
||||
"save": "Save",
|
||||
"statistic": {
|
||||
"activeUsers": "Active members",
|
||||
"count": "Count",
|
||||
|
||||
@ -58,6 +58,7 @@
|
||||
@update-status="updateStatus"
|
||||
@reload-contribution="reloadContribution"
|
||||
@update-contributions="refetch"
|
||||
@search-for-email="query = $event"
|
||||
/>
|
||||
|
||||
<BPagination
|
||||
@ -152,7 +153,7 @@ const fields = computed(
|
||||
formatter: (value) => formatDateOrDash(value),
|
||||
},
|
||||
{ key: 'moderatorId', label: t('moderator.moderator') },
|
||||
{ key: 'editCreation', label: t('chat') },
|
||||
{ key: 'editCreation', label: t('details') },
|
||||
{ key: 'confirm', label: t('save') },
|
||||
],
|
||||
// confirmed contributions
|
||||
@ -181,7 +182,7 @@ const fields = computed(
|
||||
formatter: (value) => formatDateOrDash(value),
|
||||
},
|
||||
{ key: 'confirmedBy', label: t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: t('chat') },
|
||||
{ key: 'chatCreation', label: t('details') },
|
||||
],
|
||||
// denied contributions
|
||||
[
|
||||
@ -209,7 +210,7 @@ const fields = computed(
|
||||
formatter: (value) => formatDateOrDash(value),
|
||||
},
|
||||
{ key: 'deniedBy', label: t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: t('chat') },
|
||||
{ key: 'chatCreation', label: t('details') },
|
||||
],
|
||||
// deleted contributions
|
||||
[
|
||||
@ -237,7 +238,7 @@ const fields = computed(
|
||||
formatter: (value) => formatDateOrDash(value),
|
||||
},
|
||||
{ key: 'deletedBy', label: t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: t('chat') },
|
||||
{ key: 'chatCreation', label: t('details') },
|
||||
],
|
||||
// all contributions
|
||||
[
|
||||
@ -266,7 +267,7 @@ const fields = computed(
|
||||
formatter: (value) => formatDateOrDash(value),
|
||||
},
|
||||
{ key: 'confirmedBy', label: t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: t('chat') },
|
||||
{ key: 'chatCreation', label: t('details') },
|
||||
],
|
||||
][tabIndex.value],
|
||||
)
|
||||
|
||||
109
admin/src/pages/ProjectBranding.vue
Normal file
109
admin/src/pages/ProjectBranding.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="project-branding">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<span class="h2">{{ $t('projectBranding.title') }}</span>
|
||||
<div>
|
||||
<BButton
|
||||
v-if="store.state.moderator.roles.includes('ADMIN')"
|
||||
variant="primary"
|
||||
data-test="project-branding-add-btn"
|
||||
font-scale="2"
|
||||
class="me-3"
|
||||
:title="$t('projectBranding.addTooltip')"
|
||||
:disabled="isAddButtonDisabled"
|
||||
@click="createEntry"
|
||||
>
|
||||
<IBiPlus />
|
||||
</BButton>
|
||||
<BButton
|
||||
:animation="animation"
|
||||
data-test="project-branding-refresh-btn"
|
||||
font-scale="2"
|
||||
@click="refetch"
|
||||
>
|
||||
<IBiArrowClockwise />
|
||||
</BButton>
|
||||
</div>
|
||||
</div>
|
||||
<BListGroup>
|
||||
<BRow>
|
||||
<BCol cols="3" class="ms-1">{{ $t('name') }} + {{ $t('link') }}</BCol>
|
||||
<BCol cols="2">{{ $t('alias') }}</BCol>
|
||||
<BCol cols="2" :title="$t('projectBranding.newUserToSpaceTooltip')">
|
||||
{{ $t('projectBranding.newUserToSpace') }}
|
||||
</BCol>
|
||||
<BCol cols="3">{{ $t('logo') }}</BCol>
|
||||
<BCol v-if="store.state.moderator.roles.includes('ADMIN')" cols="1">
|
||||
{{ $t('actions') }}
|
||||
</BCol>
|
||||
</BRow>
|
||||
<BListGroupItem v-for="item in projectBrandings" :key="item.id">
|
||||
<project-branding-item
|
||||
:item="item"
|
||||
@update:item="handleUpdateItem"
|
||||
@deleted:item="handleDeleteItem"
|
||||
/>
|
||||
</BListGroupItem>
|
||||
</BListGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, watch, ref } from 'vue'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { useStore } from 'vuex'
|
||||
import { useAppToast } from '@/composables/useToast'
|
||||
import { projectBrandings as projectBrandingsQuery } from '@/graphql/projectBranding.graphql'
|
||||
|
||||
const { toastError } = useAppToast()
|
||||
const store = useStore()
|
||||
|
||||
const { result, loading, refetch, error } = useQuery(projectBrandingsQuery, null, {
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
|
||||
const projectBrandings = ref([])
|
||||
|
||||
const isAddButtonDisabled = computed(() => {
|
||||
return projectBrandings.value.some((item) => item.id === undefined)
|
||||
})
|
||||
|
||||
watch(
|
||||
result,
|
||||
() => {
|
||||
projectBrandings.value = result.value?.projectBrandings || []
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function createEntry() {
|
||||
projectBrandings.value.push({
|
||||
id: undefined,
|
||||
name: '',
|
||||
alias: '',
|
||||
description: undefined,
|
||||
spaceId: undefined,
|
||||
spaceUrl: undefined,
|
||||
newUserToSpace: false,
|
||||
logoUrl: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdateItem(updatedItem) {
|
||||
const index = projectBrandings.value.findIndex(
|
||||
(item) => item.id === updatedItem.id || item.id === undefined,
|
||||
)
|
||||
projectBrandings.value.splice(index, 1, updatedItem)
|
||||
}
|
||||
|
||||
function handleDeleteItem(id) {
|
||||
const index = projectBrandings.value.findIndex((item) => item.id === id)
|
||||
projectBrandings.value.splice(index, 1)
|
||||
}
|
||||
|
||||
watch(error, () => {
|
||||
if (error.value) toastError(error.value.message)
|
||||
})
|
||||
|
||||
const animation = computed(() => (loading.value ? 'spin' : ''))
|
||||
</script>
|
||||
@ -15,6 +15,11 @@ vi.mock('@/composables/useToast', () => ({
|
||||
toastSuccess: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: () => ({
|
||||
query: {},
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock icon components
|
||||
const mockIconComponent = {
|
||||
|
||||
@ -43,8 +43,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, watchEffect } from 'vue'
|
||||
import { ref, reactive, computed, watch, watchEffect, onMounted } from 'vue'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { searchUsers } from '../graphql/searchUsers.js'
|
||||
import useCreationMonths from '../composables/useCreationMonths'
|
||||
import SearchUserTable from '../components/Tables/SearchUserTable'
|
||||
@ -68,6 +69,7 @@ const response = ref()
|
||||
|
||||
const { creationLabel } = useCreationMonths()
|
||||
const { toastSuccess } = useAppToast()
|
||||
const route = useRoute()
|
||||
|
||||
const { result, refetch } = useQuery(searchUsers, {
|
||||
query: criteria.value,
|
||||
@ -105,6 +107,13 @@ const deletedUserSearch = () => {
|
||||
refetch()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const searchQuery = route.query.search
|
||||
if (searchQuery) {
|
||||
criteria.value = searchQuery
|
||||
}
|
||||
})
|
||||
|
||||
const fields = computed(() => [
|
||||
{ key: 'email', label: t('e_mail') },
|
||||
{ key: 'firstName', label: t('firstname') },
|
||||
|
||||
@ -36,6 +36,11 @@ const routes = [
|
||||
name: 'federation',
|
||||
component: () => import('@/pages/FederationVisualize.vue'),
|
||||
},
|
||||
{
|
||||
path: '/projectBranding',
|
||||
name: 'projectBranding',
|
||||
component: () => import('@/pages/ProjectBranding.vue'),
|
||||
},
|
||||
{
|
||||
path: '/:catchAll(.*)',
|
||||
name: 'NotFound',
|
||||
|
||||
@ -17,13 +17,13 @@ const CONFIG = require('./src/config')
|
||||
|
||||
const path = require('path')
|
||||
|
||||
export default defineConfig(({ command }) => {
|
||||
export default defineConfig(async ({ command }) => {
|
||||
const { vitePluginGraphqlLoader } = await import('vite-plugin-graphql-loader')
|
||||
if (command === 'serve') {
|
||||
CONFIG.ADMIN_HOSTING = 'nodejs'
|
||||
} else {
|
||||
CONFIG.ADMIN_HOSTING = 'nginx'
|
||||
}
|
||||
// Check config
|
||||
validate(schema, CONFIG)
|
||||
// make sure that all urls used in browser have the same protocol to prevent mixed content errors
|
||||
validate(browserUrls, [
|
||||
@ -77,8 +77,11 @@ export default defineConfig(({ command }) => {
|
||||
WALLET_AUTH_PATH: CONFIG.WALLET_AUTH_PATH ?? null,
|
||||
WALLET_LOGIN_PATH: CONFIG.WALLET_LOGIN_URL ?? null, // null,
|
||||
DEBUG_DISABLE_AUTH: CONFIG.DEBUG_DISABLE_AUTH ?? null, // null,
|
||||
HUMHUB_ACTIVE: CONFIG.HUMHUB_ACTIVE ?? null, // null,
|
||||
HUMHUB_API_URL: CONFIG.HUMHUB_API_URL ?? null, // null,
|
||||
// CONFIG_VERSION: CONFIG.CONFIG_VERSION, // null,
|
||||
}),
|
||||
vitePluginGraphqlLoader(),
|
||||
commonjs(),
|
||||
],
|
||||
build: {
|
||||
|
||||
@ -10,34 +10,37 @@ import path from 'path'
|
||||
// },
|
||||
// })
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [Vue()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./test/vitest.setup.js'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'json', 'html'],
|
||||
exclude: ['node_modules/**', 'src/assets/**', '**/*.{spec,test}.js'],
|
||||
lines: 95,
|
||||
export default defineConfig(async () => {
|
||||
const { vitePluginGraphqlLoader } = await import('vite-plugin-graphql-loader')
|
||||
return {
|
||||
plugins: [Vue(), vitePluginGraphqlLoader()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./test/vitest.setup.js'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'json', 'html'],
|
||||
exclude: ['node_modules/**', 'src/assets/**', '**/*.{spec,test}.js'],
|
||||
lines: 95,
|
||||
},
|
||||
include: ['**/?(*.)+(spec|test).js?(x)'],
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': path.resolve(__dirname, './src/$1'),
|
||||
'\\.(css|less)$': 'identity-obj-proxy',
|
||||
},
|
||||
transformMode: {
|
||||
web: [/\.[jt]sx$/],
|
||||
},
|
||||
deps: {
|
||||
inline: [/vee-validate/, 'vitest-canvas-mock'],
|
||||
},
|
||||
},
|
||||
include: ['**/?(*.)+(spec|test).js?(x)'],
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': path.resolve(__dirname, './src/$1'),
|
||||
'\\.(css|less)$': 'identity-obj-proxy',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
|
||||
},
|
||||
transformMode: {
|
||||
web: [/\.[jt]sx$/],
|
||||
},
|
||||
deps: {
|
||||
inline: [/vee-validate/, 'vitest-canvas-mock'],
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -4241,7 +4241,7 @@ graphql-tag@^2.12.6, graphql-tag@^2.4.2:
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
graphql@^16.9.0:
|
||||
graphql@^16.8.1, graphql@^16.9.0:
|
||||
version "16.10.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.10.0.tgz#24c01ae0af6b11ea87bf55694429198aaa8e220c"
|
||||
integrity sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==
|
||||
@ -5014,7 +5014,7 @@ lru-cache@^5.1.1:
|
||||
dependencies:
|
||||
yallist "^3.0.2"
|
||||
|
||||
magic-string@^0.30.11, magic-string@^0.30.12, magic-string@^0.30.14:
|
||||
magic-string@^0.30.10, magic-string@^0.30.11, magic-string@^0.30.12, magic-string@^0.30.14:
|
||||
version "0.30.17"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
|
||||
integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
|
||||
@ -5657,6 +5657,11 @@ prop-types@^15.7.2:
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.13.1"
|
||||
|
||||
property-expr@^2.0.5:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8"
|
||||
integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==
|
||||
|
||||
proto-list@~1.2.1:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||
@ -6484,6 +6489,11 @@ throttle-debounce@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz#ec5549d84e053f043c9fd0f2a6dd892ff84456b1"
|
||||
integrity sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==
|
||||
|
||||
tiny-case@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03"
|
||||
integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==
|
||||
|
||||
tinybench@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
||||
@ -6543,6 +6553,11 @@ toidentifier@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
|
||||
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||
|
||||
toposort@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
|
||||
integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==
|
||||
|
||||
tough-cookie@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.0.0.tgz#6b6518e2b5c070cf742d872ee0f4f92d69eac1af"
|
||||
@ -6608,6 +6623,11 @@ type-fest@^0.20.2:
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||
|
||||
type-fest@^2.19.0:
|
||||
version "2.19.0"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
|
||||
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
|
||||
|
||||
type-is@~1.6.18:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
@ -6823,6 +6843,15 @@ vite-plugin-environment@^1.1.3:
|
||||
resolved "https://registry.yarnpkg.com/vite-plugin-environment/-/vite-plugin-environment-1.1.3.tgz#d01a04abb2f69730a4866c9c9db51d3dab74645b"
|
||||
integrity sha512-9LBhB0lx+2lXVBEWxFZC+WO7PKEyE/ykJ7EPWCq95NEcCpblxamTbs5Dm3DLBGzwODpJMEnzQywJU8fw6XGGGA==
|
||||
|
||||
vite-plugin-graphql-loader@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/vite-plugin-graphql-loader/-/vite-plugin-graphql-loader-4.0.4.tgz#cf6c599b3e5fa32bf2b768983da68f7beccc8486"
|
||||
integrity sha512-lYnpQ2luV2fcuXmOJADljuktfMbDW00Y+6QS+Ek8Jz1Vdzlj/51LSGJwZqyjJ24a5YQ+o29Hr6el/5+nlZetvg==
|
||||
dependencies:
|
||||
graphql "^16.8.1"
|
||||
graphql-tag "^2.12.6"
|
||||
magic-string "^0.30.10"
|
||||
|
||||
vite@3.2.10:
|
||||
version "3.2.10"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.10.tgz#7ac79fead82cfb6b5bf65613cd82fba6dcc81340"
|
||||
@ -7145,6 +7174,16 @@ yocto-queue@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
yup@^1.6.1:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/yup/-/yup-1.6.1.tgz#8defcff9daaf9feac178029c0e13b616563ada4b"
|
||||
integrity sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==
|
||||
dependencies:
|
||||
property-expr "^2.0.5"
|
||||
tiny-case "^1.0.3"
|
||||
toposort "^2.0.2"
|
||||
type-fest "^2.19.0"
|
||||
|
||||
zen-observable-ts@^0.8.21:
|
||||
version "0.8.21"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d"
|
||||
|
||||
@ -6,7 +6,7 @@ GRAPHIQL=false
|
||||
GDT_ACTIVE=false
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=
|
||||
|
||||
@ -6,7 +6,7 @@ GRAPHIQL=false
|
||||
GDT_API_URL=https://gdt.gradido.net
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=
|
||||
|
||||
@ -8,7 +8,7 @@ GRAPHIQL=false
|
||||
GDT_API_URL=$GDT_API_URL
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=$DB_USER
|
||||
DB_PASSWORD=$DB_PASSWORD
|
||||
|
||||
@ -1 +1 @@
|
||||
v18.7.0
|
||||
v18.20.7
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
##################################################################################
|
||||
# BASE ###########################################################################
|
||||
##################################################################################
|
||||
FROM node:18.7.0-alpine3.16 as base
|
||||
FROM node:18.20.7-alpine3.21 as base
|
||||
|
||||
# ENVs (available in production aswell, can be overwritten by commandline or env file)
|
||||
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
|
||||
|
||||
@ -7,7 +7,7 @@ module.exports = {
|
||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
lines: 80,
|
||||
lines: 79,
|
||||
},
|
||||
},
|
||||
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "2.4.5",
|
||||
"version": "2.5.1",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
@ -69,10 +69,10 @@
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/sodium-native": "^2.3.5",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"apollo-server-testing": "^2.25.2",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.4",
|
||||
@ -92,9 +92,9 @@
|
||||
"nodemon": "^2.0.7",
|
||||
"prettier": "^2.8.7",
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-node": "^10.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^3.14.0",
|
||||
"typescript": "^4.3.4"
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignore": [
|
||||
|
||||
@ -49,14 +49,16 @@ export class GmsUser {
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
user.alias &&
|
||||
user.gmsPublishName === PublishNameType.PUBLISH_NAME_ALIAS_OR_INITALS
|
||||
(user.gmsPublishName as PublishNameType) === PublishNameType.PUBLISH_NAME_ALIAS_OR_INITALS
|
||||
) {
|
||||
return user.alias
|
||||
}
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
((!user.alias && user.gmsPublishName === PublishNameType.PUBLISH_NAME_ALIAS_OR_INITALS) ||
|
||||
user.gmsPublishName === PublishNameType.PUBLISH_NAME_INITIALS)
|
||||
((!user.alias &&
|
||||
(user.gmsPublishName as PublishNameType) ===
|
||||
PublishNameType.PUBLISH_NAME_ALIAS_OR_INITALS) ||
|
||||
(user.gmsPublishName as PublishNameType) === PublishNameType.PUBLISH_NAME_INITIALS)
|
||||
) {
|
||||
return (
|
||||
this.firstUpperCaseSecondLowerCase(user.firstName) +
|
||||
@ -68,16 +70,18 @@ export class GmsUser {
|
||||
private getGmsFirstName(user: dbUser): string | null | undefined {
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
(user.gmsPublishName === PublishNameType.PUBLISH_NAME_FIRST ||
|
||||
user.gmsPublishName === PublishNameType.PUBLISH_NAME_FIRST_INITIAL ||
|
||||
user.gmsPublishName === PublishNameType.PUBLISH_NAME_FULL)
|
||||
((user.gmsPublishName as PublishNameType) === PublishNameType.PUBLISH_NAME_FIRST ||
|
||||
(user.gmsPublishName as PublishNameType) === PublishNameType.PUBLISH_NAME_FIRST_INITIAL ||
|
||||
(user.gmsPublishName as PublishNameType) === PublishNameType.PUBLISH_NAME_FULL)
|
||||
) {
|
||||
return user.firstName
|
||||
}
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
((!user.alias && user.gmsPublishName === PublishNameType.PUBLISH_NAME_ALIAS_OR_INITALS) ||
|
||||
user.gmsPublishName === PublishNameType.PUBLISH_NAME_INITIALS)
|
||||
((!user.alias &&
|
||||
(user.gmsPublishName as PublishNameType) ===
|
||||
PublishNameType.PUBLISH_NAME_ALIAS_OR_INITALS) ||
|
||||
(user.gmsPublishName as PublishNameType) === PublishNameType.PUBLISH_NAME_INITIALS)
|
||||
) {
|
||||
// return this.firstUpperCaseSecondLowerCase(user.firstName)
|
||||
return null // cause to delete firstname in gms
|
||||
@ -85,10 +89,16 @@ export class GmsUser {
|
||||
}
|
||||
|
||||
private getGmsLastName(user: dbUser): string | null | undefined {
|
||||
if (user.gmsAllowed && user.gmsPublishName === PublishNameType.PUBLISH_NAME_FULL) {
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
(user.gmsPublishName as PublishNameType) === PublishNameType.PUBLISH_NAME_FULL
|
||||
) {
|
||||
return user.lastName
|
||||
}
|
||||
if (user.gmsAllowed && user.gmsPublishName === PublishNameType.PUBLISH_NAME_FIRST_INITIAL) {
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
(user.gmsPublishName as PublishNameType) === PublishNameType.PUBLISH_NAME_FIRST_INITIAL
|
||||
) {
|
||||
return this.firstUpperCaseSecondLowerCase(user.lastName)
|
||||
}
|
||||
return null // cause to delete lastname in gms
|
||||
@ -114,8 +124,10 @@ export class GmsUser {
|
||||
private getGmsCountryCode(user: dbUser): string | undefined {
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
(user.emailContact?.gmsPublishPhone === GmsPublishPhoneType.GMS_PUBLISH_PHONE_COUNTRY ||
|
||||
user.emailContact?.gmsPublishPhone === GmsPublishPhoneType.GMS_PUBLISH_PHONE_FULL)
|
||||
((user.emailContact?.gmsPublishPhone as GmsPublishPhoneType) ===
|
||||
GmsPublishPhoneType.GMS_PUBLISH_PHONE_COUNTRY ||
|
||||
(user.emailContact?.gmsPublishPhone as GmsPublishPhoneType) ===
|
||||
GmsPublishPhoneType.GMS_PUBLISH_PHONE_FULL)
|
||||
) {
|
||||
return user.emailContact?.countryCode
|
||||
}
|
||||
@ -124,7 +136,8 @@ export class GmsUser {
|
||||
private getGmsPhone(user: dbUser): string | undefined {
|
||||
if (
|
||||
user.gmsAllowed &&
|
||||
user.emailContact?.gmsPublishPhone === GmsPublishPhoneType.GMS_PUBLISH_PHONE_FULL
|
||||
(user.emailContact?.gmsPublishPhone as GmsPublishPhoneType) ===
|
||||
GmsPublishPhoneType.GMS_PUBLISH_PHONE_FULL
|
||||
) {
|
||||
return user.emailContact?.phone
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ProjectBranding } from '@entity/ProjectBranding'
|
||||
import { SignJWT } from 'jose'
|
||||
import { IRequestOptions, IRestResponse, RestClient } from 'typed-rest-client'
|
||||
|
||||
@ -8,6 +9,8 @@ import { backendLogger as logger } from '@/server/logger'
|
||||
import { PostUserLoggingView } from './logging/PostUserLogging.view'
|
||||
import { GetUser } from './model/GetUser'
|
||||
import { PostUser } from './model/PostUser'
|
||||
import { Space } from './model/Space'
|
||||
import { SpacesResponse } from './model/SpacesResponse'
|
||||
import { UsersResponse } from './model/UsersResponse'
|
||||
|
||||
/**
|
||||
@ -61,16 +64,28 @@ export class HumHubClient {
|
||||
return token
|
||||
}
|
||||
|
||||
public async createAutoLoginUrl(username: string) {
|
||||
public async createAutoLoginUrl(username: string, project?: string | null) {
|
||||
const secret = new TextEncoder().encode(CONFIG.HUMHUB_JWT_KEY)
|
||||
logger.info(`user ${username} as username for humhub auto-login`)
|
||||
const token = await new SignJWT({ username })
|
||||
let redirectLink: string | undefined
|
||||
if (project) {
|
||||
const projectBranding = await ProjectBranding.findOne({
|
||||
where: { alias: project },
|
||||
select: { spaceUrl: true },
|
||||
})
|
||||
if (projectBranding?.spaceUrl) {
|
||||
redirectLink = projectBranding.spaceUrl
|
||||
}
|
||||
}
|
||||
const token = await new SignJWT({ username, redirectLink })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(CONFIG.JWT_EXPIRES_IN)
|
||||
.sign(secret)
|
||||
|
||||
return `${CONFIG.HUMHUB_API_URL}user/auth/external?authclient=jwt&jwt=${token}`
|
||||
return `${CONFIG.HUMHUB_API_URL}${
|
||||
CONFIG.HUMHUB_API_URL.endsWith('/') ? '' : '/'
|
||||
}user/auth/external?authclient=jwt&jwt=${token}`
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,6 +201,40 @@ export class HumHubClient {
|
||||
throw new LogError('error deleting user', { userId: humhubUserId, response })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// new RestClient('gradido', 'api/v1/')
|
||||
// get spaces from humhub
|
||||
// https://marketplace.humhub.com/module/rest/docs/html/space.html#tag/Space/paths/~1space/get
|
||||
public async spaces(page = 0, limit = 20): Promise<SpacesResponse | null> {
|
||||
const options = await this.createRequestOptions({ page, limit })
|
||||
const response = await this.restClient.get<SpacesResponse>('/api/v1/space', options)
|
||||
if (response.statusCode !== 200) {
|
||||
throw new LogError('error requesting spaces from humhub', response)
|
||||
}
|
||||
return response.result
|
||||
}
|
||||
|
||||
// get space by id from humhub instance
|
||||
// https://marketplace.humhub.com/module/rest/docs/html/space.html#tag/Space/paths/~1space~1{id}/get
|
||||
public async space(spaceId: number): Promise<Space | null> {
|
||||
const options = await this.createRequestOptions()
|
||||
const response = await this.restClient.get<Space>(`/api/v1/space/${spaceId}`, options)
|
||||
if (response.statusCode !== 200) {
|
||||
throw new LogError('error requesting space from humhub', response)
|
||||
}
|
||||
return response.result
|
||||
}
|
||||
|
||||
// add user to space
|
||||
// https://marketplace.humhub.com/module/rest/docs/html/space.html#tag/Membership/paths/~1space~1%7Bid%7D~1membership~1%7BuserId%7D/post
|
||||
public async addUserToSpace(userId: number, spaceId: number): Promise<void> {
|
||||
const options = await this.createRequestOptions()
|
||||
const response = await this.restClient.create(
|
||||
`/api/v1/space/${spaceId}/membership/${userId}`,
|
||||
{ userId },
|
||||
options,
|
||||
)
|
||||
if (response.statusCode !== 200) {
|
||||
throw new LogError('error adding user to space', response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
backend/src/apis/humhub/model/Space.ts
Normal file
7
backend/src/apis/humhub/model/Space.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export class Space {
|
||||
id: number
|
||||
guid: string
|
||||
name: string
|
||||
description: string
|
||||
url: string
|
||||
}
|
||||
8
backend/src/apis/humhub/model/SpacesResponse.ts
Normal file
8
backend/src/apis/humhub/model/SpacesResponse.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Space } from './Space'
|
||||
|
||||
export interface SpacesResponse {
|
||||
total: number
|
||||
page: number
|
||||
pages: number
|
||||
results: Space[]
|
||||
}
|
||||
@ -8,4 +8,5 @@ export const ADMIN_RIGHTS = [
|
||||
RIGHTS.COMMUNITY_BY_UUID,
|
||||
RIGHTS.COMMUNITY_BY_IDENTIFIER,
|
||||
RIGHTS.HOME_COMMUNITY,
|
||||
RIGHTS.PROJECT_BRANDING_MUTATE,
|
||||
]
|
||||
|
||||
@ -9,4 +9,5 @@ export const INALIENABLE_RIGHTS = [
|
||||
RIGHTS.QUERY_TRANSACTION_LINK,
|
||||
RIGHTS.QUERY_OPT_IN,
|
||||
RIGHTS.CHECK_USERNAME,
|
||||
RIGHTS.PROJECT_BRANDING_BANNER,
|
||||
]
|
||||
|
||||
@ -8,6 +8,7 @@ export enum RIGHTS {
|
||||
QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK',
|
||||
QUERY_OPT_IN = 'QUERY_OPT_IN',
|
||||
CHECK_USERNAME = 'CHECK_USERNAME',
|
||||
PROJECT_BRANDING_BANNER = 'PROJECT_BRANDING_BANNER',
|
||||
// User
|
||||
VERIFY_LOGIN = 'VERIFY_LOGIN',
|
||||
BALANCE = 'BALANCE',
|
||||
@ -39,6 +40,8 @@ export enum RIGHTS {
|
||||
USER = 'USER',
|
||||
GMS_USER_PLAYGROUND = 'GMS_USER_PLAYGROUND',
|
||||
HUMHUB_AUTO_LOGIN = 'HUMHUB_AUTO_LOGIN',
|
||||
PROJECT_BRANDING_VIEW = 'PROJECT_BRANDING_VIEW',
|
||||
LIST_HUMHUB_SPACES = 'LIST_HUMHUB_SPACES',
|
||||
// Moderator
|
||||
SEARCH_USERS = 'SEARCH_USERS',
|
||||
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',
|
||||
@ -64,4 +67,5 @@ export enum RIGHTS {
|
||||
COMMUNITY_BY_IDENTIFIER = 'COMMUNITY_BY_IDENTIFIER',
|
||||
HOME_COMMUNITY = 'HOME_COMMUNITY',
|
||||
COMMUNITY_UPDATE = 'COMMUNITY_UPDATE',
|
||||
PROJECT_BRANDING_MUTATE = 'PROJECT_BRANDING_MUTATE',
|
||||
}
|
||||
|
||||
@ -31,4 +31,6 @@ export const USER_RIGHTS = [
|
||||
RIGHTS.USER,
|
||||
RIGHTS.GMS_USER_PLAYGROUND,
|
||||
RIGHTS.HUMHUB_AUTO_LOGIN,
|
||||
RIGHTS.PROJECT_BRANDING_VIEW,
|
||||
RIGHTS.LIST_HUMHUB_SPACES,
|
||||
]
|
||||
|
||||
@ -81,8 +81,8 @@ const loginServer = {
|
||||
}
|
||||
|
||||
const email = {
|
||||
EMAIL: process.env.EMAIL === 'true' ?? false,
|
||||
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' ?? false,
|
||||
EMAIL: process.env.EMAIL === 'true' || false,
|
||||
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || false,
|
||||
EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER ?? 'stage1@gradido.net',
|
||||
EMAIL_USERNAME: process.env.EMAIL_USERNAME ?? '',
|
||||
EMAIL_SENDER: process.env.EMAIL_SENDER ?? 'info@gradido.net',
|
||||
@ -123,7 +123,7 @@ const federation = {
|
||||
process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER ?? 60000,
|
||||
),
|
||||
FEDERATION_XCOM_SENDCOINS_ENABLED:
|
||||
process.env.FEDERATION_XCOM_SENDCOINS_ENABLED === 'true' ?? false,
|
||||
process.env.FEDERATION_XCOM_SENDCOINS_ENABLED === 'true' || false,
|
||||
// default value for community-uuid is equal uuid of stage-3
|
||||
FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID:
|
||||
process.env.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID ?? '56a55482-909e-46a4-bfa2-cd025e894ebc',
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
GMS_ACTIVE,
|
||||
GRAPHIQL,
|
||||
HUMHUB_ACTIVE,
|
||||
HUMHUB_API_URL,
|
||||
LOG4JS_CONFIG,
|
||||
LOGIN_APP_SECRET,
|
||||
LOGIN_SERVER_KEY,
|
||||
@ -44,6 +45,7 @@ export const schema = Joi.object({
|
||||
GMS_ACTIVE,
|
||||
GRAPHIQL,
|
||||
HUMHUB_ACTIVE,
|
||||
HUMHUB_API_URL,
|
||||
LOG4JS_CONFIG,
|
||||
LOGIN_APP_SECRET,
|
||||
LOGIN_SERVER_KEY,
|
||||
@ -281,11 +283,6 @@ export const schema = Joi.object({
|
||||
.when('GMS_ACTIVE', { is: true, then: Joi.required(), otherwise: Joi.optional() })
|
||||
.description('The secret postfix for the GMS webhook endpoint'),
|
||||
|
||||
HUMHUB_API_URL: Joi.string()
|
||||
.uri({ scheme: ['http', 'https'] })
|
||||
.when('HUMHUB_ACTIVE', { is: true, then: Joi.required(), otherwise: Joi.optional() })
|
||||
.description('The API URL for HumHub integration'),
|
||||
|
||||
HUMHUB_JWT_KEY: Joi.string()
|
||||
.min(1)
|
||||
.when('HUMHUB_ACTIVE', {
|
||||
|
||||
@ -40,6 +40,7 @@ export const sendAccountActivationEmail = (data: {
|
||||
language: string
|
||||
activationLink: string
|
||||
timeDurationObject: Record<string, unknown>
|
||||
logoUrl?: string | null
|
||||
}): Promise<Record<string, unknown> | boolean | null> => {
|
||||
return sendEmailTranslated({
|
||||
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
|
||||
@ -50,6 +51,7 @@ export const sendAccountActivationEmail = (data: {
|
||||
locale: data.language,
|
||||
activationLink: data.activationLink,
|
||||
timeDurationObject: data.timeDurationObject,
|
||||
logoUrl: data.logoUrl,
|
||||
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
||||
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||
communityURL: CONFIG.COMMUNITY_URL,
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
if logoUrl
|
||||
img(src=logoUrl, alt="Banner", style="max-width: 680px; max-height: 250px;border-radius:20px")
|
||||
h2= t('emails.accountActivation.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
|
||||
@ -30,4 +30,8 @@ export class CreateUserArgs {
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
redeemCode?: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
project?: string | null
|
||||
}
|
||||
|
||||
@ -14,4 +14,8 @@ export class UnsecureLoginArgs {
|
||||
@Field(() => Int, { nullable: true })
|
||||
@IsInt()
|
||||
publisherId?: number | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
project?: string | null
|
||||
}
|
||||
|
||||
44
backend/src/graphql/input/ProjectBrandingInput.ts
Normal file
44
backend/src/graphql/input/ProjectBrandingInput.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { IsString, IsOptional, MaxLength, IsNumber, IsBoolean, IsUrl } from 'class-validator'
|
||||
import { InputType, Field, Int } from 'type-graphql'
|
||||
|
||||
@InputType()
|
||||
export class ProjectBrandingInput {
|
||||
@Field(() => Int, { nullable: true })
|
||||
@IsOptional()
|
||||
id: number | null | undefined
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
name: string
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@MaxLength(32)
|
||||
alias: string
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description: string | null | undefined
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
spaceId: number | null | undefined
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsUrl()
|
||||
spaceUrl: string | null | undefined
|
||||
|
||||
@Field(() => Boolean)
|
||||
@IsBoolean()
|
||||
newUserToSpace: boolean
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsUrl()
|
||||
logoUrl: string | null | undefined
|
||||
}
|
||||
@ -3,12 +3,22 @@ import { User } from '@entity/User'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
|
||||
import { PublishNameType } from '@enum/PublishNameType'
|
||||
|
||||
import { PublishNameLogic } from '@/data/PublishName.logic'
|
||||
|
||||
@ObjectType()
|
||||
export class Contribution {
|
||||
constructor(contribution: dbContribution, user?: User | null) {
|
||||
this.id = contribution.id
|
||||
this.firstName = user ? user.firstName : null
|
||||
this.lastName = user ? user.lastName : null
|
||||
this.firstName = user?.firstName ?? null
|
||||
this.lastName = user?.lastName ?? null
|
||||
this.email = user?.emailContact?.email ?? null
|
||||
this.username = user?.alias ?? null
|
||||
if (user) {
|
||||
const publishNameLogic = new PublishNameLogic(user)
|
||||
this.humhubUsername = publishNameLogic.getUsername(user.humhubPublishName as PublishNameType)
|
||||
}
|
||||
this.amount = contribution.amount
|
||||
this.memo = contribution.memo
|
||||
this.createdAt = contribution.createdAt
|
||||
@ -37,6 +47,15 @@ export class Contribution {
|
||||
@Field(() => String, { nullable: true })
|
||||
lastName: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
email: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
username: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
humhubUsername: string | null
|
||||
|
||||
@Field(() => Decimal)
|
||||
amount: Decimal
|
||||
|
||||
|
||||
19
backend/src/graphql/model/Pagination.ts
Normal file
19
backend/src/graphql/model/Pagination.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class Pagination {
|
||||
@Field(() => Int)
|
||||
total: number
|
||||
|
||||
@Field(() => Int)
|
||||
page: number
|
||||
|
||||
@Field(() => Int)
|
||||
pages: number
|
||||
|
||||
constructor(total: number, page: number, pages: number) {
|
||||
this.total = total
|
||||
this.page = page
|
||||
this.pages = pages
|
||||
}
|
||||
}
|
||||
33
backend/src/graphql/model/ProjectBranding.ts
Normal file
33
backend/src/graphql/model/ProjectBranding.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { ProjectBranding as dbProjectBranding } from '@entity/ProjectBranding'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class ProjectBranding {
|
||||
constructor(projectBranding: dbProjectBranding) {
|
||||
Object.assign(this, projectBranding)
|
||||
}
|
||||
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => String)
|
||||
name: string
|
||||
|
||||
@Field(() => String)
|
||||
alias: string
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
description: string | null
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
spaceId: number | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
spaceUrl: string | null
|
||||
|
||||
@Field(() => Boolean)
|
||||
newUserToSpace: boolean
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
logoUrl: string | null
|
||||
}
|
||||
25
backend/src/graphql/model/Space.ts
Normal file
25
backend/src/graphql/model/Space.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
|
||||
import { Space as HumhubSpace } from '@/apis/humhub/model/Space'
|
||||
|
||||
@ObjectType()
|
||||
export class Space {
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => String)
|
||||
guid: string
|
||||
|
||||
@Field(() => String)
|
||||
name: string
|
||||
|
||||
@Field(() => String)
|
||||
description: string
|
||||
|
||||
@Field(() => String)
|
||||
url: string
|
||||
|
||||
constructor(data: HumhubSpace) {
|
||||
Object.assign(this, data)
|
||||
}
|
||||
}
|
||||
20
backend/src/graphql/model/SpaceList.ts
Normal file
20
backend/src/graphql/model/SpaceList.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
|
||||
import { SpacesResponse } from '@/apis/humhub/model/SpacesResponse'
|
||||
|
||||
import { Pagination } from './Pagination'
|
||||
import { Space } from './Space'
|
||||
|
||||
@ObjectType()
|
||||
export class SpaceList {
|
||||
@Field(() => Pagination)
|
||||
pagination: Pagination
|
||||
|
||||
@Field(() => [Space])
|
||||
results: Space[]
|
||||
|
||||
constructor(data: SpacesResponse) {
|
||||
this.pagination = new Pagination(data.total, data.page, data.pages)
|
||||
this.results = data.results
|
||||
}
|
||||
}
|
||||
@ -153,9 +153,9 @@ describe('ContributionMessageResolver', () => {
|
||||
message: 'Test',
|
||||
},
|
||||
})
|
||||
expect(logger.debug).toBeCalledTimes(4)
|
||||
expect(logger.debug).toBeCalledTimes(5)
|
||||
expect(logger.debug).toHaveBeenNthCalledWith(
|
||||
4,
|
||||
5,
|
||||
'use UnconfirmedContributionUserAddMessageRole',
|
||||
)
|
||||
expect(mutationResult).toEqual(
|
||||
@ -351,9 +351,9 @@ describe('ContributionMessageResolver', () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(logger.debug).toBeCalledTimes(4)
|
||||
expect(logger.debug).toBeCalledTimes(5)
|
||||
expect(logger.debug).toHaveBeenNthCalledWith(
|
||||
4,
|
||||
5,
|
||||
'use UnconfirmedContributionAdminAddMessageRole',
|
||||
)
|
||||
|
||||
@ -386,9 +386,9 @@ describe('ContributionMessageResolver', () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(logger.debug).toBeCalledTimes(4)
|
||||
expect(logger.debug).toBeCalledTimes(5)
|
||||
expect(logger.debug).toHaveBeenNthCalledWith(
|
||||
4,
|
||||
5,
|
||||
'use UnconfirmedContributionAdminAddMessageRole',
|
||||
)
|
||||
|
||||
@ -404,7 +404,7 @@ describe('ContributionMessageResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error "ContributionMessage was not sent successfully: Error: missing right ADMIN_CREATE_CONTRIBUTION_MESSAGE for user"', () => {
|
||||
expect(logger.debug).toBeCalledTimes(4)
|
||||
expect(logger.debug).toBeCalledTimes(5)
|
||||
expect(logger.error).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'missing right ADMIN_CREATE_CONTRIBUTION_MESSAGE for user',
|
||||
|
||||
@ -144,7 +144,7 @@ export class ContributionResolver {
|
||||
dbContributions.map((contribution) => {
|
||||
// filter out moderator messages for this call
|
||||
contribution.messages = contribution.messages?.filter(
|
||||
(m) => m.type !== ContributionMessageType.MODERATOR,
|
||||
(m) => (m.type as ContributionMessageType) !== ContributionMessageType.MODERATOR,
|
||||
)
|
||||
return new Contribution(contribution, user)
|
||||
}),
|
||||
@ -352,7 +352,7 @@ export class ContributionResolver {
|
||||
}
|
||||
const moderator = getUser(context)
|
||||
if (
|
||||
contribution.contributionType === ContributionType.USER &&
|
||||
(contribution.contributionType as ContributionType) === ContributionType.USER &&
|
||||
contribution.userId === moderator.id
|
||||
) {
|
||||
throw new LogError('Own contribution can not be deleted as admin')
|
||||
@ -532,8 +532,9 @@ export class ContributionResolver {
|
||||
throw new LogError('Contribution not found', id)
|
||||
}
|
||||
if (
|
||||
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
||||
(contributionToUpdate.contributionStatus as ContributionStatus) !==
|
||||
ContributionStatus.IN_PROGRESS &&
|
||||
(contributionToUpdate.contributionStatus as ContributionStatus) !== ContributionStatus.PENDING
|
||||
) {
|
||||
throw new LogError(
|
||||
'Status of the contribution is not allowed',
|
||||
|
||||
105
backend/src/graphql/resolver/ProjectBrandingResolver.ts
Normal file
105
backend/src/graphql/resolver/ProjectBrandingResolver.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { ProjectBranding as DbProjectBranding } from '@entity/ProjectBranding'
|
||||
import { Resolver, Query, Mutation, Arg, Int, Authorized, ID } from 'type-graphql'
|
||||
|
||||
import { ProjectBrandingInput } from '@input/ProjectBrandingInput'
|
||||
import { ProjectBranding } from '@model/ProjectBranding'
|
||||
import { Space } from '@model/Space'
|
||||
import { SpaceList } from '@model/SpaceList'
|
||||
|
||||
import { HumHubClient } from '@/apis/humhub/HumHubClient'
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
@Resolver(() => ProjectBranding)
|
||||
export class ProjectBrandingResolver {
|
||||
@Query(() => [ProjectBranding])
|
||||
@Authorized([RIGHTS.PROJECT_BRANDING_VIEW])
|
||||
async projectBrandings(): Promise<ProjectBranding[]> {
|
||||
return (await DbProjectBranding.find()).map(
|
||||
(entity: DbProjectBranding) => new ProjectBranding(entity),
|
||||
)
|
||||
}
|
||||
|
||||
@Query(() => ProjectBranding)
|
||||
@Authorized([RIGHTS.PROJECT_BRANDING_VIEW])
|
||||
async projectBranding(@Arg('id', () => Int) id: number): Promise<ProjectBranding> {
|
||||
const projectBrandingEntity = await DbProjectBranding.findOneBy({ id })
|
||||
if (!projectBrandingEntity) {
|
||||
throw new LogError(`Project Branding with id: ${id} not found`)
|
||||
}
|
||||
return new ProjectBranding(projectBrandingEntity)
|
||||
}
|
||||
|
||||
@Query(() => String, { nullable: true })
|
||||
@Authorized([RIGHTS.PROJECT_BRANDING_BANNER])
|
||||
async projectBrandingBanner(@Arg('alias', () => String) alias: string): Promise<string | null> {
|
||||
const projectBrandingEntity = await DbProjectBranding.findOne({
|
||||
where: { alias },
|
||||
select: { id: true, logoUrl: true },
|
||||
})
|
||||
if (!projectBrandingEntity) {
|
||||
throw new LogError(`Project Branding with alias: ${alias} not found`)
|
||||
}
|
||||
return projectBrandingEntity.logoUrl
|
||||
}
|
||||
|
||||
@Mutation(() => ProjectBranding, { nullable: true })
|
||||
@Authorized([RIGHTS.PROJECT_BRANDING_MUTATE])
|
||||
async upsertProjectBranding(
|
||||
@Arg('input') input: ProjectBrandingInput,
|
||||
): Promise<ProjectBranding | null> {
|
||||
const projectBranding = input.id
|
||||
? await DbProjectBranding.findOneOrFail({ where: { id: input.id } })
|
||||
: new DbProjectBranding()
|
||||
|
||||
Object.assign(projectBranding, input)
|
||||
await projectBranding.save()
|
||||
|
||||
return new ProjectBranding(projectBranding)
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@Authorized([RIGHTS.PROJECT_BRANDING_MUTATE])
|
||||
async deleteProjectBranding(@Arg('id', () => ID) id: number): Promise<boolean> {
|
||||
try {
|
||||
await DbProjectBranding.delete({ id })
|
||||
return true
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => Space)
|
||||
@Authorized([RIGHTS.LIST_HUMHUB_SPACES])
|
||||
async space(@Arg('id', () => ID) id: number): Promise<Space> {
|
||||
const humhub = HumHubClient.getInstance()
|
||||
if (!humhub) {
|
||||
throw new LogError('HumHub client not initialized')
|
||||
}
|
||||
|
||||
const space = await humhub.space(id)
|
||||
if (!space) {
|
||||
throw new LogError(`Error requesting space with id: ${id} from HumHub`)
|
||||
}
|
||||
return new Space(space)
|
||||
}
|
||||
|
||||
@Query(() => SpaceList)
|
||||
@Authorized([RIGHTS.LIST_HUMHUB_SPACES])
|
||||
async spaces(
|
||||
@Arg('page', () => Int, { defaultValue: 1 }) page: number,
|
||||
@Arg('limit', () => Int, { defaultValue: 20 }) limit: number,
|
||||
): Promise<SpaceList> {
|
||||
const humhub = HumHubClient.getInstance()
|
||||
if (!humhub) {
|
||||
throw new LogError('HumHub client not initialized')
|
||||
}
|
||||
const spaces = await humhub.spaces(page, limit)
|
||||
if (!spaces) {
|
||||
throw new LogError('Error requesting spaces from HumHub')
|
||||
}
|
||||
return new SpaceList(spaces)
|
||||
}
|
||||
}
|
||||
@ -266,7 +266,7 @@ export class TransactionResolver {
|
||||
// userTransactions.forEach((transaction: dbTransaction) => {
|
||||
// use normal for loop because of timing problems with await in forEach-loop
|
||||
for (const transaction of userTransactions) {
|
||||
if (transaction.typeId === TransactionTypeId.CREATION) {
|
||||
if ((transaction.typeId as TransactionTypeId) === TransactionTypeId.CREATION) {
|
||||
continue
|
||||
}
|
||||
if (transaction.linkedUserId && !involvedUserIds.includes(transaction.linkedUserId)) {
|
||||
@ -398,7 +398,7 @@ export class TransactionResolver {
|
||||
: involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
|
||||
*/
|
||||
let linkedUser: User | undefined
|
||||
if (userTransaction.typeId === TransactionTypeId.CREATION) {
|
||||
if ((userTransaction.typeId as TransactionTypeId) === TransactionTypeId.CREATION) {
|
||||
linkedUser = communityUser
|
||||
logger.debug('CREATION-linkedUser=', linkedUser)
|
||||
} else if (userTransaction.linkedUserId) {
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { getConnection, In, Point } from '@dbTools/typeorm'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { ProjectBranding } from '@entity/ProjectBranding'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { UserContact as DbUserContact } from '@entity/UserContact'
|
||||
@ -145,10 +146,10 @@ export class UserResolver {
|
||||
@Authorized([RIGHTS.LOGIN])
|
||||
@Mutation(() => User)
|
||||
async login(
|
||||
@Args() { email, password, publisherId }: UnsecureLoginArgs,
|
||||
@Args() { email, password, publisherId, project }: UnsecureLoginArgs,
|
||||
@Ctx() context: Context,
|
||||
): Promise<User> {
|
||||
logger.info(`login with ${email}, ***, ${publisherId} ...`)
|
||||
logger.info(`login with ${email}, ***, ${publisherId}, project=${project} ...`)
|
||||
email = email.trim().toLowerCase()
|
||||
let dbUser: DbUser
|
||||
|
||||
@ -177,6 +178,7 @@ export class UserResolver {
|
||||
|
||||
// request to humhub and klicktipp run in parallel
|
||||
let humhubUserPromise: Promise<IRestResponse<GetUser>> | undefined
|
||||
let projectBrandingPromise: Promise<ProjectBranding | null> | undefined
|
||||
const klicktippStatePromise = getKlicktippState(dbUser.emailContact.email)
|
||||
if (CONFIG.HUMHUB_ACTIVE && dbUser.humhubAllowed) {
|
||||
const getHumhubUser = new PostUser(dbUser)
|
||||
@ -184,8 +186,17 @@ export class UserResolver {
|
||||
getHumhubUser.account.username,
|
||||
)
|
||||
}
|
||||
if (project) {
|
||||
projectBrandingPromise = ProjectBranding.findOne({
|
||||
where: { alias: project },
|
||||
select: { spaceId: true },
|
||||
})
|
||||
}
|
||||
|
||||
if (dbUser.passwordEncryptionType !== PasswordEncryptionType.GRADIDO_ID) {
|
||||
if (
|
||||
(dbUser.passwordEncryptionType as PasswordEncryptionType) !==
|
||||
PasswordEncryptionType.GRADIDO_ID
|
||||
) {
|
||||
dbUser.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
||||
dbUser.password = await encryptPassword(dbUser, password)
|
||||
await dbUser.save()
|
||||
@ -213,11 +224,20 @@ export class UserResolver {
|
||||
})
|
||||
|
||||
await EVENT_USER_LOGIN(dbUser)
|
||||
const projectBranding = await projectBrandingPromise
|
||||
logger.debug('project branding: ', projectBranding)
|
||||
// load humhub state
|
||||
if (humhubUserPromise) {
|
||||
try {
|
||||
const result = await humhubUserPromise
|
||||
user.humhubAllowed = result?.result?.account.status === 1
|
||||
if (user.humhubAllowed) {
|
||||
let spaceId = null
|
||||
if (projectBranding) {
|
||||
spaceId = projectBranding.spaceId
|
||||
}
|
||||
void syncHumhub(null, dbUser, spaceId)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("couldn't reach out to humhub, disable for now", e)
|
||||
user.humhubAllowed = false
|
||||
@ -249,11 +269,12 @@ export class UserResolver {
|
||||
language,
|
||||
publisherId = null,
|
||||
redeemCode = null,
|
||||
project = null,
|
||||
}: CreateUserArgs,
|
||||
): Promise<User> {
|
||||
logger.addContext('user', 'unknown')
|
||||
logger.info(
|
||||
`createUser(email=${email}, firstName=${firstName}, lastName=${lastName}, language=${language}, publisherId=${publisherId}, redeemCode =${redeemCode})`,
|
||||
`createUser(email=${email}, firstName=${firstName}, lastName=${lastName}, language=${language}, publisherId=${publisherId}, redeemCode=${redeemCode}, project=${project})`,
|
||||
)
|
||||
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
||||
// default int publisher_id = 0;
|
||||
@ -308,7 +329,13 @@ export class UserResolver {
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
let projectBrandingPromise: Promise<ProjectBranding | null> | undefined
|
||||
if (project) {
|
||||
projectBrandingPromise = ProjectBranding.findOne({
|
||||
where: { alias: project },
|
||||
select: { logoUrl: true, spaceId: true },
|
||||
})
|
||||
}
|
||||
const gradidoID = await newGradidoID()
|
||||
|
||||
const eventRegisterRedeem = Event(
|
||||
@ -356,6 +383,7 @@ export class UserResolver {
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
let projectBranding: ProjectBranding | null | undefined
|
||||
try {
|
||||
dbUser = await queryRunner.manager.save(dbUser).catch((error) => {
|
||||
throw new LogError('Error while saving dbUser', error)
|
||||
@ -373,8 +401,11 @@ export class UserResolver {
|
||||
|
||||
const activationLink = `${
|
||||
CONFIG.EMAIL_LINK_VERIFICATION
|
||||
}${emailContact.emailVerificationCode.toString()}${redeemCode ? `/${redeemCode}` : ''}`
|
||||
}${emailContact.emailVerificationCode.toString()}${redeemCode ? `/${redeemCode}` : ''}${
|
||||
project ? `?project=` + project : ''
|
||||
}`
|
||||
|
||||
projectBranding = projectBrandingPromise ? await projectBrandingPromise : undefined
|
||||
void sendAccountActivationEmail({
|
||||
firstName,
|
||||
lastName,
|
||||
@ -382,6 +413,7 @@ export class UserResolver {
|
||||
language,
|
||||
activationLink,
|
||||
timeDurationObject: getTimeDurationObject(CONFIG.EMAIL_CODE_VALID_TIME),
|
||||
logoUrl: projectBranding?.logoUrl,
|
||||
})
|
||||
logger.info(`sendAccountActivationEmail of ${firstName}.${lastName} to ${email}`)
|
||||
|
||||
@ -397,7 +429,11 @@ export class UserResolver {
|
||||
}
|
||||
logger.info('createUser() successful...')
|
||||
if (CONFIG.HUMHUB_ACTIVE) {
|
||||
void syncHumhub(null, dbUser)
|
||||
let spaceId: number | null = null
|
||||
if (projectBranding) {
|
||||
spaceId = projectBranding.spaceId
|
||||
}
|
||||
void syncHumhub(null, dbUser, spaceId)
|
||||
}
|
||||
|
||||
if (redeemCode) {
|
||||
@ -538,7 +574,7 @@ export class UserResolver {
|
||||
|
||||
// Sign into Klicktipp
|
||||
// TODO do we always signUp the user? How to handle things with old users?
|
||||
if (userContact.emailOptInTypeId === OptInType.EMAIL_OPT_IN_REGISTER) {
|
||||
if ((userContact.emailOptInTypeId as OptInType) === OptInType.EMAIL_OPT_IN_REGISTER) {
|
||||
try {
|
||||
await subscribe(userContact.email, user.language, user.firstName, user.lastName)
|
||||
logger.debug(
|
||||
@ -771,7 +807,10 @@ export class UserResolver {
|
||||
|
||||
@Authorized([RIGHTS.HUMHUB_AUTO_LOGIN])
|
||||
@Query(() => String)
|
||||
async authenticateHumhubAutoLogin(@Ctx() context: Context): Promise<string> {
|
||||
async authenticateHumhubAutoLogin(
|
||||
@Ctx() context: Context,
|
||||
@Arg('project', () => String, { nullable: true }) project?: string | null,
|
||||
): Promise<string> {
|
||||
logger.info(`authenticateHumhubAutoLogin()...`)
|
||||
const dbUser = getUser(context)
|
||||
const humhubClient = HumHubClient.getInstance()
|
||||
@ -790,7 +829,7 @@ export class UserResolver {
|
||||
if (humhubUser.account.status !== 1) {
|
||||
throw new LogError('user status is not 1', humhubUser.account.status)
|
||||
}
|
||||
return await humhubClient.createAutoLoginUrl(humhubUser.account.username)
|
||||
return await humhubClient.createAutoLoginUrl(humhubUser.account.username, project)
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.SEARCH_ADMIN_USERS])
|
||||
|
||||
@ -2,6 +2,7 @@ import { Point } from '@dbTools/typeorm'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { UpdateUserInfosArgs } from '@/graphql/arg/UpdateUserInfosArgs'
|
||||
import { GmsPublishLocationType } from '@/graphql/enum/GmsPublishLocationType'
|
||||
import { PublishNameType } from '@/graphql/enum/PublishNameType'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
@ -44,13 +45,14 @@ export function compareGmsRelevantUserSettings(
|
||||
}
|
||||
if (
|
||||
updateUserInfosArgs.gmsPublishLocation &&
|
||||
orgUser.gmsPublishLocation !== updateUserInfosArgs.gmsPublishLocation
|
||||
(orgUser.gmsPublishLocation as GmsPublishLocationType) !==
|
||||
updateUserInfosArgs.gmsPublishLocation
|
||||
) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
updateUserInfosArgs.gmsPublishName &&
|
||||
orgUser.gmsPublishName !== updateUserInfosArgs.gmsPublishName
|
||||
(orgUser.gmsPublishName as PublishNameType) !== updateUserInfosArgs.gmsPublishName
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
|
||||
import { testEnvironment, cleanDB, contributionDateFormatter } from '@test/helpers'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
import { userFactory } from '@/seeds/factory/user'
|
||||
import { login, createContribution, adminCreateContribution } from '@/seeds/graphql/mutations'
|
||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
@ -14,6 +15,8 @@ import { getOpenCreations, getUserCreation } from './creations'
|
||||
|
||||
jest.mock('@/password/EncryptorUtils')
|
||||
|
||||
CONFIG.HUMHUB_ACTIVE = false
|
||||
|
||||
let mutate: ApolloServerTestClient['mutate'], con: Connection
|
||||
let testEnv: {
|
||||
mutate: ApolloServerTestClient['mutate']
|
||||
@ -272,6 +275,7 @@ describe('util/creation', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOpenCreations', () => {
|
||||
beforeAll(() => {
|
||||
// enable Fake timers
|
||||
|
||||
@ -6,10 +6,17 @@ import { ExecutedHumhubAction, syncUser } from '@/apis/humhub/syncUser'
|
||||
import { UpdateUserInfosArgs } from '@/graphql/arg/UpdateUserInfosArgs'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
/**
|
||||
* Syncs the user with humhub
|
||||
* @param updateUserInfosArg
|
||||
* @param user
|
||||
* @returns humhub user id or undefined
|
||||
*/
|
||||
export async function syncHumhub(
|
||||
updateUserInfosArg: UpdateUserInfosArgs | null,
|
||||
user: User,
|
||||
): Promise<void> {
|
||||
spaceId?: number | null,
|
||||
): Promise<number | undefined> {
|
||||
// check for humhub relevant changes
|
||||
if (
|
||||
updateUserInfosArg &&
|
||||
@ -47,4 +54,9 @@ export async function syncHumhub(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
result: ExecutedHumhubAction[result as ExecutedHumhubAction],
|
||||
})
|
||||
if (spaceId && humhubUser) {
|
||||
await humhubClient.addUserToSpace(humhubUser.id, spaceId)
|
||||
logger.debug(`user added to space ${spaceId}`)
|
||||
}
|
||||
return user.id
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ export abstract class AbstractUnconfirmedContributionRole {
|
||||
throw new LogError('Month of contribution can not be changed')
|
||||
}
|
||||
|
||||
if (this.self.contributionStatus === ContributionStatus.CONFIRMED) {
|
||||
if ((this.self.contributionStatus as ContributionStatus) === ContributionStatus.CONFIRMED) {
|
||||
throw new LogError('the contribution is already confirmed, cannot be changed anymore')
|
||||
}
|
||||
|
||||
|
||||
@ -35,9 +35,10 @@ export class UnconfirmedContributionUserRole extends AbstractUnconfirmedContribu
|
||||
throw new LogError('Can not update contribution of another user', this.self, user.id)
|
||||
}
|
||||
// only admins and moderators can update it when status is other than progress or pending
|
||||
const contributionStatus = this.self.contributionStatus as ContributionStatus
|
||||
if (
|
||||
this.self.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||
this.self.contributionStatus !== ContributionStatus.PENDING
|
||||
contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||
contributionStatus !== ContributionStatus.PENDING
|
||||
) {
|
||||
throw new LogError(
|
||||
'Contribution can not be updated due to status',
|
||||
|
||||
@ -22,7 +22,7 @@ export class UnconfirmedContributionUserAddMessageRole extends AbstractUnconfirm
|
||||
|
||||
protected update(): void {
|
||||
if (
|
||||
this.self.contributionStatus === ContributionStatus.IN_PROGRESS ||
|
||||
(this.self.contributionStatus as ContributionStatus) === ContributionStatus.IN_PROGRESS ||
|
||||
this.self.resubmissionAt !== null
|
||||
) {
|
||||
this.self.contributionStatus = ContributionStatus.PENDING
|
||||
@ -38,9 +38,10 @@ export class UnconfirmedContributionUserAddMessageRole extends AbstractUnconfirm
|
||||
}
|
||||
// only admins and moderators can update it when status is other than progress or pending
|
||||
// but we are in the user add message role.. we are currently not admin or moderator
|
||||
const contributionStatus = this.self.contributionStatus as ContributionStatus
|
||||
if (
|
||||
this.self.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||
this.self.contributionStatus !== ContributionStatus.PENDING
|
||||
contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||
contributionStatus !== ContributionStatus.PENDING
|
||||
) {
|
||||
throw new LogError(
|
||||
'Contribution can not be updated due to status',
|
||||
|
||||
@ -52,7 +52,8 @@ export const userFactory = async (
|
||||
if (user.deletedAt) {
|
||||
dbUser.deletedAt = user.deletedAt
|
||||
}
|
||||
if (user.role && (user.role === RoleNames.ADMIN || user.role === RoleNames.MODERATOR)) {
|
||||
const userRole = user.role as RoleNames
|
||||
if (userRole && (userRole === RoleNames.ADMIN || userRole === RoleNames.MODERATOR)) {
|
||||
await setUserRole(dbUser, user.role)
|
||||
}
|
||||
await dbUser.save()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
v18.7.0
|
||||
v18.20.7
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
"devDependencies": {
|
||||
"@types/joi": "^17.2.3",
|
||||
"@types/node": "^17.0.21",
|
||||
"typescript": "^4.3.5",
|
||||
"typescript": "^4.9.5",
|
||||
"mkdirp": "^3.0.1",
|
||||
"ncp": "^2.0.0"
|
||||
},
|
||||
|
||||
@ -117,6 +117,11 @@ export const HUMHUB_ACTIVE = Joi.boolean()
|
||||
.default(false)
|
||||
.required()
|
||||
|
||||
export const HUMHUB_API_URL = Joi.string()
|
||||
.uri({ scheme: ['http', 'https'] })
|
||||
.when('HUMHUB_ACTIVE', { is: true, then: Joi.required(), otherwise: Joi.optional() })
|
||||
.description('The API URL for HumHub integration')
|
||||
|
||||
export const LOG_LEVEL = Joi.string()
|
||||
.valid('all', 'mark', 'trace', 'debug', 'info', 'warn', 'error', 'fatal', 'off')
|
||||
.description('set log level')
|
||||
|
||||
@ -64,7 +64,7 @@ ncp@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
|
||||
integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==
|
||||
|
||||
typescript@^4.3.5:
|
||||
typescript@^4.9.5:
|
||||
version "4.9.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
DB_HOST=localhost
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
CONFIG_VERSION=$DATABASE_CONFIG_VERSION
|
||||
|
||||
DB_HOST=localhost
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=$DB_USER
|
||||
DB_PASSWORD=$DB_PASSWORD
|
||||
|
||||
@ -1 +1 @@
|
||||
v18.7.0
|
||||
v18.20.7
|
||||
@ -1,7 +1,7 @@
|
||||
##################################################################################
|
||||
# BASE ###########################################################################
|
||||
##################################################################################
|
||||
FROM node:18.7.0-alpine3.16 as base
|
||||
FROM node:18.20.7-alpine3.21 as base
|
||||
|
||||
# ENVs (available in production aswell, can be overwritten by commandline or env file)
|
||||
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm'
|
||||
|
||||
@Entity('project_brandings')
|
||||
export class ProjectBranding extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'name', type: 'varchar', length: 255 })
|
||||
name: string
|
||||
|
||||
@Column({ name: 'alias', type: 'varchar', length: 32 })
|
||||
alias: string
|
||||
|
||||
@Column({ name: 'description', type: 'text', nullable: true, default: null })
|
||||
description: string | null
|
||||
|
||||
@Column({ name: 'space_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||
spaceId: number | null
|
||||
|
||||
@Column({ name: 'space_url', type: 'varchar', length: 255, nullable: true, default: null })
|
||||
spaceUrl: string | null
|
||||
|
||||
@Column({ name: 'new_user_to_space', type: 'tinyint', width: 1, default: 0 })
|
||||
newUserToSpace: boolean
|
||||
|
||||
@Column({ name: 'logo_url', type: 'varchar', length: 255, nullable: true, default: null })
|
||||
logoUrl: string | null
|
||||
}
|
||||
1
database/entity/ProjectBranding.ts
Normal file
1
database/entity/ProjectBranding.ts
Normal file
@ -0,0 +1 @@
|
||||
export { ProjectBranding } from './0088-create_project_brandings/ProjectBranding'
|
||||
@ -2,6 +2,7 @@ import { ContributionLink } from './ContributionLink'
|
||||
import { LoginElopageBuys } from './LoginElopageBuys'
|
||||
import { LoginEmailOptIn } from './LoginEmailOptIn'
|
||||
import { Migration } from './Migration'
|
||||
import { ProjectBranding } from './ProjectBranding'
|
||||
import { Transaction } from './Transaction'
|
||||
import { TransactionLink } from './TransactionLink'
|
||||
import { User } from './User'
|
||||
@ -26,6 +27,7 @@ export const entities = [
|
||||
LoginElopageBuys,
|
||||
LoginEmailOptIn,
|
||||
Migration,
|
||||
ProjectBranding,
|
||||
PendingTransaction,
|
||||
Transaction,
|
||||
TransactionLink,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
|
||||
26
database/migrations/0088-create_project_brandings.ts
Normal file
26
database/migrations/0088-create_project_brandings.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/* MIGRATION TO CREATE THE project_brandings table
|
||||
*
|
||||
* This migration creates the `community` and 'communityfederation' tables in the `apollo` database (`gradido_community`).
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`
|
||||
CREATE TABLE \`project_brandings\`(
|
||||
\`id\` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
\`name\` VARCHAR(255) NOT NULL,
|
||||
\`alias\` VARCHAR(32) NOT NULL,
|
||||
\`description\` TEXT NULL DEFAULT NULL,
|
||||
\`space_id\` INT UNSIGNED NULL DEFAULT NULL,
|
||||
\`space_url\` VARCHAR(255) NULL DEFAULT NULL,
|
||||
\`new_user_to_space\` TINYINT(1) NOT NULL DEFAULT FALSE,
|
||||
\`logo_url\` VARCHAR(255) NULL DEFAULT NULL,
|
||||
PRIMARY KEY(\`id\`)
|
||||
) ENGINE = InnoDB;
|
||||
`)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn('DROP TABLE `project_brandings`')
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-database",
|
||||
"version": "2.4.5",
|
||||
"version": "2.5.1",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/database",
|
||||
@ -37,8 +37,8 @@
|
||||
"mkdirp": "^3.0.1",
|
||||
"ncp": "^2.0.0",
|
||||
"prettier": "^2.8.7",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "^4.3.5"
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/uuid": "^8.3.4",
|
||||
|
||||
@ -9,17 +9,12 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@cspotcode/source-map-consumer@0.8.0":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
|
||||
integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==
|
||||
|
||||
"@cspotcode/source-map-support@0.6.1":
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz#118511f316e2e87ee4294761868e254d3da47960"
|
||||
integrity sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==
|
||||
"@cspotcode/source-map-support@^0.8.0":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
|
||||
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
|
||||
dependencies:
|
||||
"@cspotcode/source-map-consumer" "0.8.0"
|
||||
"@jridgewell/trace-mapping" "0.3.9"
|
||||
|
||||
"@eslint-community/eslint-plugin-eslint-comments@^3.2.1":
|
||||
version "3.2.1"
|
||||
@ -80,6 +75,24 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||
|
||||
"@jridgewell/resolve-uri@^3.0.3":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
|
||||
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
|
||||
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
|
||||
|
||||
"@jridgewell/trace-mapping@0.3.9":
|
||||
version "0.3.9"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
|
||||
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
@ -2353,12 +2366,12 @@ ts-mysql-migrate@^1.0.2:
|
||||
"@types/mysql" "^2.15.8"
|
||||
mysql "^2.18.1"
|
||||
|
||||
ts-node@^10.2.1:
|
||||
version "10.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.2.1.tgz#4cc93bea0a7aba2179497e65bb08ddfc198b3ab5"
|
||||
integrity sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==
|
||||
ts-node@^10.9.2:
|
||||
version "10.9.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"
|
||||
integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==
|
||||
dependencies:
|
||||
"@cspotcode/source-map-support" "0.6.1"
|
||||
"@cspotcode/source-map-support" "^0.8.0"
|
||||
"@tsconfig/node10" "^1.0.7"
|
||||
"@tsconfig/node12" "^1.0.7"
|
||||
"@tsconfig/node14" "^1.0.0"
|
||||
@ -2369,6 +2382,7 @@ ts-node@^10.2.1:
|
||||
create-require "^1.1.0"
|
||||
diff "^4.0.1"
|
||||
make-error "^1.1.1"
|
||||
v8-compile-cache-lib "^3.0.1"
|
||||
yn "3.1.1"
|
||||
|
||||
tsconfig-paths@^3.14.1:
|
||||
@ -2440,10 +2454,10 @@ typeorm@^0.3.16:
|
||||
uuid "^9.0.0"
|
||||
yargs "^17.6.2"
|
||||
|
||||
typescript@^4.3.5:
|
||||
version "4.3.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
|
||||
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
|
||||
typescript@^4.9.5:
|
||||
version "4.9.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||
|
||||
unbox-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
@ -2482,6 +2496,11 @@ uuid@^9.0.0:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||
|
||||
v8-compile-cache-lib@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
|
||||
|
||||
which-boxed-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
# helper functions
|
||||
log_step() {
|
||||
local message="$1"
|
||||
echo -e "\e[34m$message\e[0m" > /dev/tty # blue in console
|
||||
echo "<p style="color:blue">$message</p>" >> "$UPDATE_HTML" # blue in html
|
||||
}
|
||||
|
||||
# check for parameter
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: Please provide a branch name as the first argument."
|
||||
@ -75,18 +83,18 @@ TODAY=$(date +"%Y-%m-%d")
|
||||
exec > >(tee -a $UPDATE_HTML) 2>&1
|
||||
|
||||
# configure nginx for the update-page
|
||||
echo 'Configuring nginx to serve the update-page' >> $UPDATE_HTML
|
||||
log_step 'Configuring nginx to serve the update-page'
|
||||
ln -sf $SCRIPT_DIR/nginx/sites-available/update-page.conf $SCRIPT_DIR/nginx/sites-enabled/default
|
||||
sudo /etc/init.d/nginx restart
|
||||
|
||||
# stop all services
|
||||
echo 'Stop and delete all Gradido services' >> $UPDATE_HTML
|
||||
log_step "Stop and delete all Gradido services"
|
||||
pm2 delete all
|
||||
pm2 save
|
||||
|
||||
# git
|
||||
BRANCH=$1
|
||||
echo "Starting with git pull - branch:$BRANCH" >> $UPDATE_HTML
|
||||
log_step "Starting with git pull - branch:$BRANCH"
|
||||
cd $PROJECT_ROOT
|
||||
# TODO: this overfetches alot, but ensures we can use start.sh with tags
|
||||
git fetch --all
|
||||
@ -99,7 +107,7 @@ export BUILD_COMMIT="$(git rev-parse HEAD)"
|
||||
# *** set FEDERATION_PORT from FEDERATION_COMMUNITY_APIS and create gradido-federation.conf file
|
||||
rm -f $NGINX_CONFIG_DIR/gradido.conf.tmp
|
||||
rm -f $NGINX_CONFIG_DIR/gradido-federation.conf.locations
|
||||
echo "====================================================================================================" >> $UPDATE_HTML
|
||||
log_step "===================================================================================================="
|
||||
IFS="," read -a API_ARRAY <<< $FEDERATION_COMMUNITY_APIS
|
||||
for api in "${API_ARRAY[@]}"
|
||||
do
|
||||
@ -109,18 +117,18 @@ do
|
||||
FEDERATION_PORT=${FEDERATION_COMMUNITY_API_PORT:-5000}
|
||||
FEDERATION_PORT=$(($FEDERATION_PORT + $port))
|
||||
export FEDERATION_PORT
|
||||
echo "create ngingx config: location /api/$FEDERATION_APIVERSION to http://127.0.0.1:$FEDERATION_PORT" >> $UPDATE_HTML
|
||||
log_step "create ngingx config: location /api/$FEDERATION_APIVERSION to http://127.0.0.1:$FEDERATION_PORT"
|
||||
envsubst '$FEDERATION_APIVERSION, $FEDERATION_PORT' < $NGINX_CONFIG_DIR/gradido-federation.conf.template >> $NGINX_CONFIG_DIR/gradido-federation.conf.locations
|
||||
done
|
||||
unset FEDERATION_APIVERSION
|
||||
unset FEDERATION_PORT
|
||||
echo "====================================================================================================" >> $UPDATE_HTML
|
||||
log_step "===================================================================================================="
|
||||
|
||||
# *** 2nd read gradido-federation.conf file in env variable to be replaced in 3rd step
|
||||
export FEDERATION_NGINX_CONF=$(< $NGINX_CONFIG_DIR/gradido-federation.conf.locations)
|
||||
|
||||
# *** 3rd generate gradido nginx config including federation modules per api-version
|
||||
echo 'Generate new gradido nginx config' >> $UPDATE_HTML
|
||||
log_step 'Generate new gradido nginx config'
|
||||
case "$URL_PROTOCOL" in
|
||||
'https') TEMPLATE_FILE="gradido.conf.ssl.template" ;;
|
||||
*) TEMPLATE_FILE="gradido.conf.template" ;;
|
||||
@ -132,18 +140,20 @@ rm $NGINX_CONFIG_DIR/gradido.conf.tmp
|
||||
rm $NGINX_CONFIG_DIR/gradido-federation.conf.locations
|
||||
|
||||
# Generate update-page.conf from template
|
||||
echo 'Generate new update-page nginx config' >> $UPDATE_HTML
|
||||
log_step 'Generate new update-page nginx config'
|
||||
case "$URL_PROTOCOL" in
|
||||
'https') TEMPLATE_FILE="update-page.conf.ssl.template" ;;
|
||||
*) TEMPLATE_FILE="update-page.conf.template" ;;
|
||||
esac
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $NGINX_CONFIG_DIR/$TEMPLATE_FILE > $NGINX_CONFIG_DIR/update-page.conf
|
||||
|
||||
log_step 'Clean tmp and yarn cache'
|
||||
# Clean tmp folder - remove yarn files
|
||||
find /tmp -name "yarn--*" -exec rm -r {} \;
|
||||
# Clean user cache folder
|
||||
rm -Rf ~/.cache/yarn
|
||||
|
||||
log_step 'Remove all node_modules and build folders'
|
||||
# Remove node_modules folders
|
||||
# we had problems with corrupted node_modules folder
|
||||
rm -Rf $PROJECT_ROOT/database/node_modules
|
||||
@ -164,6 +174,7 @@ rm -Rf $PROJECT_ROOT/admin/build
|
||||
rm -Rf $PROJECT_ROOT/dht-node/build
|
||||
rm -Rf $PROJECT_ROOT/federation/build
|
||||
|
||||
log_step 'Regenerate .env files'
|
||||
# Regenerate .env files
|
||||
cp -f $PROJECT_ROOT/database/.env $PROJECT_ROOT/database/.env.bak
|
||||
cp -f $PROJECT_ROOT/backend/.env $PROJECT_ROOT/backend/.env.bak
|
||||
@ -179,7 +190,7 @@ envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/dht-node/.env
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/federation/.env.template > $PROJECT_ROOT/federation/.env
|
||||
|
||||
# Install & build database
|
||||
echo 'Updating database' >> $UPDATE_HTML
|
||||
log_step 'Updating database'
|
||||
cd $PROJECT_ROOT/database
|
||||
yarn install
|
||||
yarn build
|
||||
@ -191,13 +202,13 @@ else
|
||||
fi
|
||||
|
||||
# Install & build config
|
||||
echo 'Updating config' >> $UPDATE_HTML
|
||||
log_step 'Updating config'
|
||||
cd $PROJECT_ROOT/config
|
||||
yarn install
|
||||
yarn build
|
||||
|
||||
# Install & build backend
|
||||
echo 'Updating backend' >> $UPDATE_HTML
|
||||
log_step 'Updating backend'
|
||||
cd $PROJECT_ROOT/backend
|
||||
# TODO maybe handle this differently?
|
||||
unset NODE_ENV
|
||||
@ -211,35 +222,27 @@ export NODE_ENV=production
|
||||
|
||||
|
||||
# Install & build frontend
|
||||
echo 'Updating frontend' >> $UPDATE_HTML
|
||||
log_step 'Updating frontend'
|
||||
cd $PROJECT_ROOT/frontend
|
||||
# TODO maybe handle this differently?
|
||||
unset NODE_ENV
|
||||
nvm use
|
||||
nvm install
|
||||
npm i -g yarn
|
||||
yarn install
|
||||
yarn build
|
||||
# TODO maybe handle this differently?
|
||||
export NODE_ENV=production
|
||||
|
||||
# Install & build admin
|
||||
echo 'Updating admin' >> $UPDATE_HTML
|
||||
log_step 'Updating admin'
|
||||
cd $PROJECT_ROOT/admin
|
||||
# TODO maybe handle this differently?
|
||||
unset NODE_ENV
|
||||
nvm use
|
||||
nvm install
|
||||
npm i -g yarn
|
||||
yarn install
|
||||
yarn build
|
||||
# TODO maybe handle this differently?
|
||||
export NODE_ENV=production
|
||||
|
||||
nvm use default
|
||||
|
||||
# Install & build dht-node
|
||||
echo 'Updating dht-node' >> $UPDATE_HTML
|
||||
log_step 'Updating dht-node'
|
||||
cd $PROJECT_ROOT/dht-node
|
||||
# TODO maybe handle this differently?
|
||||
unset NODE_ENV
|
||||
@ -249,7 +252,7 @@ yarn build
|
||||
export NODE_ENV=production
|
||||
|
||||
# Install & build federation
|
||||
echo 'Updating federation' >> $UPDATE_HTML
|
||||
log_step 'Updating federation'
|
||||
cd $PROJECT_ROOT/federation
|
||||
# TODO maybe handle this differently?
|
||||
unset NODE_ENV
|
||||
@ -268,34 +271,34 @@ if [ ! -z $FEDERATION_DHT_TOPIC ]; then
|
||||
pm2 start --name gradido-dht-node "yarn --cwd $PROJECT_ROOT/dht-node start" -l $GRADIDO_LOG_PATH/pm2.dht-node.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
|
||||
pm2 save
|
||||
else
|
||||
echo "=====================================================================" >> $UPDATE_HTML
|
||||
echo "WARNING: FEDERATION_DHT_TOPIC not configured. DHT-Node not started..." >> $UPDATE_HTML
|
||||
echo "=====================================================================" >> $UPDATE_HTML
|
||||
fi
|
||||
log_step "====================================================================="
|
||||
log_step "WARNING: FEDERATION_DHT_TOPIC not configured. DHT-Node not started..."
|
||||
log_step "====================================================================="
|
||||
fi
|
||||
|
||||
# set FEDERATION_PORT from FEDERATION_COMMUNITY_APIS
|
||||
IFS="," read -a API_ARRAY <<< $FEDERATION_COMMUNITY_APIS
|
||||
for api in "${API_ARRAY[@]}"
|
||||
do
|
||||
export FEDERATION_API=$api
|
||||
echo "FEDERATION_API=$FEDERATION_API" >> $UPDATE_HTML
|
||||
log_step "FEDERATION_API=$FEDERATION_API"
|
||||
export MODULENAME=gradido-federation-$api
|
||||
echo "MODULENAME=$MODULENAME" >> $UPDATE_HTML
|
||||
log_step "MODULENAME=$MODULENAME"
|
||||
# calculate port by remove '_' and add value of api to baseport
|
||||
port=${api//_/}
|
||||
FEDERATION_PORT=${FEDERATION_COMMUNITY_API_PORT:-5000}
|
||||
FEDERATION_PORT=$(($FEDERATION_PORT + $port))
|
||||
export FEDERATION_PORT
|
||||
echo "====================================================" >> $UPDATE_HTML
|
||||
echo " start $MODULENAME listening on port=$FEDERATION_PORT" >> $UPDATE_HTML
|
||||
echo "====================================================" >> $UPDATE_HTML
|
||||
log_step "===================================================="
|
||||
log_step " start $MODULENAME listening on port=$FEDERATION_PORT"
|
||||
log_step "===================================================="
|
||||
# pm2 delete $MODULENAME
|
||||
pm2 start --name $MODULENAME "yarn --cwd $PROJECT_ROOT/federation start" -l $GRADIDO_LOG_PATH/pm2.$MODULENAME.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
|
||||
pm2 save
|
||||
done
|
||||
|
||||
# let nginx showing gradido
|
||||
echo 'Configuring nginx to serve gradido again' >> $UPDATE_HTML
|
||||
log_step 'Configuring nginx to serve gradido again'
|
||||
ln -sf $SCRIPT_DIR/nginx/sites-available/gradido.conf $SCRIPT_DIR/nginx/sites-enabled/default
|
||||
sudo /etc/init.d/nginx restart
|
||||
|
||||
|
||||
@ -124,11 +124,11 @@ sudo systemctl daemon-reload
|
||||
# setup https with certbot
|
||||
certbot certonly --nginx --non-interactive --agree-tos --domains $COMMUNITY_HOST --email $COMMUNITY_SUPPORT_MAIL
|
||||
|
||||
# Install node 16. with nvm, with nodesource is depracted
|
||||
# Install node 18
|
||||
sudo -u gradido bash -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash'
|
||||
# Close and reopen your terminal to start using nvm or run the following to use it now:
|
||||
sudo -u gradido bash -c 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"'
|
||||
sudo -u gradido bash -c '. $HOME/.nvm/nvm.sh && nvm install 16' # first installed version will be set to default automatic
|
||||
sudo -u gradido bash -c '. $HOME/.nvm/nvm.sh && nvm install v18.20.7' # first installed version will be set to default automatic
|
||||
|
||||
# Install yarn
|
||||
sudo -u gradido bash -c '. $HOME/.nvm/nvm.sh && npm i -g yarn'
|
||||
|
||||
15
deployment/hetzner_cloud/migration/2_5_1-2_5_2/README.md
Normal file
15
deployment/hetzner_cloud/migration/2_5_1-2_5_2/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
## Migrate from Gradido Version 2.5.1 to 2.5.2
|
||||
### What
|
||||
We need to upgrade the used nodejs version in deployment. Until now for pm2, backend, dht-node, federation nodejs 16 was used,
|
||||
but some newer npm libs don't work with this old nodejs version so we upgrade to nodejs 18.20.7
|
||||
|
||||
### What you can do now
|
||||
You need to only run this [upgradeNodeJs.sh](upgradeNodeJs.sh) with `release-2_5_2-beta` as parameter
|
||||
```bash
|
||||
cd /home/gradido/gradido/deployment/hetzner_cloud/migration/2_5_1-2_5_2
|
||||
sudo ./upgradeNodeJs.sh `release-2_5_2-beta`
|
||||
```
|
||||
|
||||
It will stop pm2, install new nodejs version + pm2 + yarn, remove nodejs 16.
|
||||
Then it will call start.sh with first parameter of ./upgradeNodeJs.sh as his first parameter
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# check for parameter
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: Please provide a branch name as the first argument."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# stop all services
|
||||
pm2 delete all
|
||||
pm2 save
|
||||
|
||||
# upgrade node js version
|
||||
nvm install 18.20.7
|
||||
nvm use 18.20.7
|
||||
nvm alias default 18.20.7
|
||||
npm install -g pm2 yarn
|
||||
nvm uninstall 16
|
||||
|
||||
# Start gradido
|
||||
sudo -u gradido $SCRIPT_PATH/start.sh $1
|
||||
@ -1,5 +1,5 @@
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=$DB_USER
|
||||
DB_PASSWORD=$DB_PASSWORD
|
||||
|
||||
@ -1 +1 @@
|
||||
v19.5.0
|
||||
v18.20.7
|
||||
@ -1,7 +1,7 @@
|
||||
##################################################################################
|
||||
# BASE ###########################################################################
|
||||
##################################################################################
|
||||
FROM node:19.5.0-alpine3.17 as base
|
||||
FROM node:18.20.7-alpine3.21 as base
|
||||
#FROM ubuntu:latest as base
|
||||
|
||||
# ENVs (available in production aswell, can be overwritten by commandline or env file)
|
||||
@ -33,10 +33,10 @@ LABEL maintainer="support@gradido.net"
|
||||
|
||||
# Install Additional Software
|
||||
## install: sodium requirements
|
||||
RUN apk add --no-cache --virtual build-deps python3 alpine-sdk autoconf libtool automake && \
|
||||
mkdir -p /prebuilds && cd /prebuilds && npm init -y && npm install sodium-native@3.1.1 && \
|
||||
apk del build-deps
|
||||
ENV SODIUM_NATIVE_PREBUILD=/prebuilds/node_modules/sodium-native/
|
||||
#RUN apk add --no-cache --virtual build-deps python3 alpine-sdk autoconf libtool automake && \
|
||||
# mkdir -p /prebuilds && cd /prebuilds && npm init -y && npm install sodium-native@3.1.1 && \
|
||||
# apk del build-deps
|
||||
#ENV SODIUM_NATIVE_PREBUILD=/prebuilds/node_modules/sodium-native/
|
||||
|
||||
# Settings
|
||||
## Expose Container Port
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-dht-node",
|
||||
"version": "2.4.5",
|
||||
"version": "2.5.1",
|
||||
"description": "Gradido dht-node module",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/",
|
||||
@ -49,7 +49,7 @@
|
||||
"jest": "^27.2.4",
|
||||
"prettier": "^2.8.7",
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -2435,7 +2435,7 @@ graceful-fs@^4.2.4:
|
||||
joi "^17.13.3"
|
||||
|
||||
"gradido-database@file:../database":
|
||||
version "2.4.4"
|
||||
version "2.5.1"
|
||||
dependencies:
|
||||
"@types/uuid" "^8.3.4"
|
||||
cross-env "^7.0.3"
|
||||
@ -4667,10 +4667,10 @@ ts-mysql-migrate@^1.0.2:
|
||||
"@types/mysql" "^2.15.8"
|
||||
mysql "^2.18.1"
|
||||
|
||||
ts-node@^10.9.1:
|
||||
version "10.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
|
||||
integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
|
||||
ts-node@^10.9.2:
|
||||
version "10.9.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"
|
||||
integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==
|
||||
dependencies:
|
||||
"@cspotcode/source-map-support" "^0.8.0"
|
||||
"@tsconfig/node10" "^1.0.7"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-dlt-connector",
|
||||
"version": "2.4.5",
|
||||
"version": "2.5.1",
|
||||
"description": "Gradido DLT-Connector",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/",
|
||||
|
||||
@ -67,7 +67,7 @@ services:
|
||||
## MARIADB ##############################################
|
||||
#########################################################
|
||||
mariadb:
|
||||
image: mariadb:10.5
|
||||
image: mariadb:10.11.6
|
||||
environment:
|
||||
- MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1
|
||||
- MARIADB_USER=root
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user