Merge remote-tracking branch 'origin/master' into 3522-feature-introduce-security-in-disbursement-handshake

This commit is contained in:
clauspeterhuebner 2025-08-25 15:17:26 +02:00
commit 0a23e11af3
33 changed files with 2130 additions and 59 deletions

View File

@ -10,6 +10,7 @@ jobs:
# JOB: DOCKER BUILD PRODUCTION FRONTEND ######################################
##############################################################################
build_production_frontend:
if: startsWith(github.event.head_commit.message, 'chore(release):')
name: Docker Build Production - Frontend
runs-on: ubuntu-latest
#needs: [nothing]
@ -47,6 +48,7 @@ jobs:
# JOB: DOCKER BUILD PRODUCTION ADMIN #########################################
##############################################################################
build_production_admin:
if: startsWith(github.event.head_commit.message, 'chore(release):')
name: Docker Build Production - Admin
runs-on: ubuntu-latest
#needs: [nothing]
@ -84,6 +86,7 @@ jobs:
# JOB: DOCKER BUILD PRODUCTION BACKEND #######################################
##############################################################################
build_production_backend:
if: startsWith(github.event.head_commit.message, 'chore(release):')
name: Docker Build Production - Backend
runs-on: ubuntu-latest
#needs: [nothing]
@ -121,6 +124,7 @@ jobs:
# JOB: DOCKER BUILD PRODUCTION DHT-NODE ######################################
##############################################################################
build_production_dht-node:
if: startsWith(github.event.head_commit.message, 'chore(release):')
name: Docker Build Production - DHT-Node
runs-on: ubuntu-latest
#needs: [nothing]
@ -158,6 +162,7 @@ jobs:
# JOB: DOCKER BUILD PRODUCTION FEDERATION ######################################
##############################################################################
build_production_federation:
if: startsWith(github.event.head_commit.message, 'chore(release):')
name: Docker Build Production - Federation
runs-on: ubuntu-latest
#needs: [nothing]
@ -195,6 +200,7 @@ jobs:
# JOB: DOCKER BUILD PRODUCTION DATABASE UP ###################################
##############################################################################
build_production_database_up:
if: startsWith(github.event.head_commit.message, 'chore(release):')
name: Docker Build Production - Database up
runs-on: ubuntu-latest
#needs: [nothing]
@ -221,6 +227,7 @@ jobs:
# JOB: DOCKER BUILD PRODUCTION NGINX #########################################
##############################################################################
build_production_nginx:
if: startsWith(github.event.head_commit.message, 'chore(release):')
name: Docker Build Production - Nginx
runs-on: ubuntu-latest
#needs: [nothing]
@ -258,6 +265,7 @@ jobs:
# JOB: UPLOAD TO DOCKERHUB ###################################################
##############################################################################
upload_to_dockerhub:
if: startsWith(github.event.head_commit.message, 'chore(release):')
name: Upload to Dockerhub
runs-on: ubuntu-latest
needs: [build_production_frontend, build_production_backend, build_production_database_up, build_production_nginx]
@ -315,8 +323,6 @@ jobs:
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/database_up.tar
- name: Load Docker Image
run: docker load < /tmp/mariadb.tar
- name: Download Docker Image (Nginx)
uses: actions/download-artifact@v4
with:
@ -348,6 +354,7 @@ jobs:
##############################################################################
##############################################################################
github_tag:
if: startsWith(github.event.head_commit.message, 'chore(release):')
name: Tag latest version on Github
runs-on: ubuntu-latest
needs: [upload_to_dockerhub]

View File

@ -32,7 +32,7 @@ jobs:
uses: actions/checkout@v3
- name: Database | Build image
run: docker build --target build -t "gradido/database:build" -f database/Dockerfile .
run: docker build --target up -t "gradido/database:up" -f database/Dockerfile .
database_migration_test:
if: needs.files-changed.outputs.database == 'true' || needs.files-changed.outputs.docker-compose == 'true' || needs.files-changed.outputs.mariadb == 'true' || needs.files-changed.outputs.shared == 'true'

View File

@ -174,6 +174,49 @@ turbo frontend#dev backend#start admin#start --env-mode=loose
Tip: for local setup use a local nginx server with similar config like docker nginx [nginx.conf](./nginx/gradido.conf) but replace docker image name with localhost
### Testing
This project uses a mocked `log4js` logger for tests.
- **clearLogs()**: Clears all collected logs. Call in `beforeEach` to start with a clean slate.
- **printLogs()**: Prints all collected logs since the last call to clearLogs for debugging purposes.
- Supports log levels: `trace`, `debug`, `info`, `warn`, `error`, `fatal`.
- Logs include context and additional arguments.
- Example:
```ts
import { clearLogs, printLogs } from 'config-schema/test/testSetup'
beforeEach(() => {
clearLogs()
})
describe('test', () => {
it('test', () => {
expect(functionCall()).toBe(true)
printLogs()
})
})
```
#### Include Paths by test framework:
- jest (backend, dht-node, federation):
```ts
import { clearLogs, printLogs } from 'config-schema/test/testSetup'
```
- vitest (frontend, admin, database):
```ts
import { clearLogs, printLogs } from 'config-schema/test/testSetup.vitest'
```
- bun (shared, core):
```ts
import { clearLogs, printLogs } from 'config-schema/test/testSetup.bun'
```
#### Attention!
It isn't tested for parallel running tests yet.
Currently Modules `frontend`, `admin`, `share` and `core` running the tests in parallel,
`database`, `backend`, `dht-node` and `federation` are running the tests still serially.
### Clear
In root folder calling `yarn clear` will clear all turbo caches, node_modules and build folders of all workspaces for a clean rebuild.

View File

@ -28,6 +28,9 @@
<BListGroupItem>
{{ $t('federation.publicKey') }}&nbsp;{{ item.publicKey }}
</BListGroupItem>
<BListGroupItem v-if="item.hieroTopicId && item.foreign">
{{ $t('federation.hieroTopicId') }}&nbsp;{{ item.hieroTopicId }}
</BListGroupItem>
<BListGroupItem v-if="!item.foreign">
<editable-group
:allow-edit="$store.state.moderator.roles.includes('ADMIN')"
@ -39,6 +42,10 @@
<p style="text-wrap: nowrap">{{ $t('federation.gmsApiKey') }}&nbsp;</p>
<span class="d-block" style="overflow-x: auto">{{ gmsApiKey }}</span>
</div>
<div class="d-flex">
<p style="text-wrap: nowrap">{{ $t('federation.hieroTopicId') }}&nbsp;</p>
<span class="d-block" style="overflow-x: auto">{{ hieroTopicId }}</span>
</div>
<BFormGroup>
{{ $t('federation.coordinates') }}
<span v-if="isValidLocation">
@ -57,6 +64,11 @@
:label="$t('federation.gmsApiKey')"
id-name="home-community-api-key"
/>
<editable-groupable-label
v-model="hieroTopicId"
:label="$t('federation.hieroTopicId')"
id-name="home-community-hiero-topic-id"
/>
<coordinates v-model="location" />
</template>
</editable-group>
@ -111,9 +123,11 @@ const { toastSuccess, toastError } = useAppToast()
const details = ref(false)
const gmsApiKey = ref(item.value.gmsApiKey)
const hieroTopicId = ref(item.value.hieroTopicId)
const location = ref(item.value.location)
const originalGmsApiKey = ref(item.value.gmsApiKey)
const originalLocation = ref(item.value.location)
const originalHieroTopicId = ref(item.value.hieroTopicId)
const { mutate: updateHomeCommunityMutation } = useMutation(updateHomeCommunity)
@ -164,6 +178,7 @@ const createdAt = computed(() => {
const isLocationChanged = computed(() => originalLocation.value !== location.value)
const isGMSApiKeyChanged = computed(() => originalGmsApiKey.value !== gmsApiKey.value)
const isHieroTopicIdChanged = computed(() => originalHieroTopicId.value !== hieroTopicId.value)
const isValidLocation = computed(
() => location.value && location.value.latitude && location.value.longitude,
)
@ -178,6 +193,7 @@ const handleUpdateHomeCommunity = async () => {
uuid: item.value.uuid,
gmsApiKey: gmsApiKey.value,
location: location.value,
hieroTopicId: hieroTopicId.value,
})
if (isLocationChanged.value && isGMSApiKeyChanged.value) {
@ -187,8 +203,12 @@ const handleUpdateHomeCommunity = async () => {
} else if (isLocationChanged.value) {
toastSuccess(t('federation.toast_gmsLocationUpdated'))
}
if (isHieroTopicIdChanged.value) {
toastSuccess(t('federation.toast_hieroTopicIdUpdated'))
}
originalLocation.value = location.value
originalGmsApiKey.value = gmsApiKey.value
originalHieroTopicId.value = hieroTopicId.value
} catch (error) {
toastError(error.message)
}
@ -197,5 +217,6 @@ const handleUpdateHomeCommunity = async () => {
const resetHomeCommunityEditable = () => {
location.value = originalLocation.value
gmsApiKey.value = originalGmsApiKey.value
hieroTopicId.value = originalHieroTopicId.value
}
</script>

View File

@ -15,6 +15,7 @@ export const allCommunities = gql`
creationDate
createdAt
updatedAt
hieroTopicId
federatedCommunities {
id
apiVersion

View File

@ -1,8 +1,13 @@
import gql from 'graphql-tag'
export const updateHomeCommunity = gql`
mutation ($uuid: String!, $gmsApiKey: String, $location: Location) {
updateHomeCommunity(uuid: $uuid, gmsApiKey: $gmsApiKey, location: $location) {
mutation ($uuid: String!, $gmsApiKey: String, $location: Location, $hieroTopicId: String) {
updateHomeCommunity(
uuid: $uuid
gmsApiKey: $gmsApiKey
location: $location
hieroTopicId: $hieroTopicId
) {
id
}
}

View File

@ -96,9 +96,11 @@
"coordinates": "Koordinaten:",
"createdAt": "Erstellt am",
"gmsApiKey": "GMS API Key:",
"hieroTopicId": "Hiero Topic ID:",
"toast_gmsApiKeyAndLocationUpdated": "Der GMS Api Key und die Location wurden erfolgreich aktualisiert!",
"toast_gmsApiKeyUpdated": "Der GMS Api Key wurde erfolgreich aktualisiert!",
"toast_gmsLocationUpdated": "Die GMS Location wurde erfolgreich aktualisiert!",
"toast_hieroTopicIdUpdated": "Die Hiero Topic ID wurde erfolgreich aktualisiert!",
"gradidoInstances": "Gradido Instanzen",
"lastAnnouncedAt": "letzte Bekanntgabe",
"lastErrorAt": "Letzer Fehler am",

View File

@ -96,9 +96,11 @@
"coordinates": "Coordinates:",
"createdAt": "Created At ",
"gmsApiKey": "GMS API Key:",
"hieroTopicId": "Hiero Topic ID:",
"toast_gmsApiKeyAndLocationUpdated": "The GMS Api Key and the location have been successfully updated!",
"toast_gmsApiKeyUpdated": "The GMS Api Key has been successfully updated!",
"toast_gmsLocationUpdated": "The GMS location has been successfully updated!",
"toast_hieroTopicIdUpdated": "The Hiero Topic ID has been successfully updated!",
"gradidoInstances": "Gradido Instances",
"lastAnnouncedAt": "Last Announced",
"lastErrorAt": "last error at",

View File

@ -18,4 +18,8 @@ export class EditCommunityInput {
@Field(() => Location, { nullable: true })
@isValidLocation()
location?: Location | null
@Field(() => String, { nullable: true })
@IsString()
hieroTopicId?: string | null
}

View File

@ -39,6 +39,7 @@ export class AdminCommunityView {
this.uuid = dbCom.communityUuid
this.authenticatedAt = dbCom.authenticatedAt
this.gmsApiKey = dbCom.gmsApiKey
this.hieroTopicId = dbCom.hieroTopicId
if (dbCom.location) {
this.location = Point2Location(dbCom.location as Point)
}
@ -71,6 +72,9 @@ export class AdminCommunityView {
@Field(() => Location, { nullable: true })
location: Location | null
@Field(() => String, { nullable: true })
hieroTopicId: string | null
@Field(() => Date, { nullable: true })
creationDate: Date | null

View File

@ -13,6 +13,7 @@ export class Community {
this.uuid = dbCom.communityUuid
this.authenticatedAt = dbCom.authenticatedAt
this.gmsApiKey = dbCom.gmsApiKey
this.hieroTopicId = dbCom.hieroTopicId
}
@Field(() => Int)
@ -41,4 +42,7 @@ export class Community {
@Field(() => String, { nullable: true })
gmsApiKey: string | null
@Field(() => String, { nullable: true })
hieroTopicId: string | null
}

View File

@ -80,7 +80,7 @@ export class CommunityResolver {
@Authorized([RIGHTS.COMMUNITY_UPDATE])
@Mutation(() => Community)
async updateHomeCommunity(
@Args() { uuid, gmsApiKey, location }: EditCommunityInput,
@Args() { uuid, gmsApiKey, location, hieroTopicId }: EditCommunityInput,
): Promise<Community> {
const homeCom = await getCommunityByUuid(uuid)
if (!homeCom) {
@ -89,11 +89,16 @@ export class CommunityResolver {
if (homeCom.foreign) {
throw new LogError('Error: Only the HomeCommunity could be modified!')
}
if (homeCom.gmsApiKey !== gmsApiKey || homeCom.location !== location) {
if (
homeCom.gmsApiKey !== gmsApiKey ||
homeCom.location !== location ||
homeCom.hieroTopicId !== hieroTopicId
) {
homeCom.gmsApiKey = gmsApiKey ?? null
if (location) {
homeCom.location = Location2Point(location)
}
homeCom.hieroTopicId = hieroTopicId ?? null
await DbCommunity.save(homeCom)
}
return new Community(homeCom)

View File

@ -423,13 +423,13 @@ describe('Contribution Links', () => {
])
})
it('returns an error if memo is longer than 255 characters', async () => {
it('returns an error if memo is longer than 512 characters', async () => {
jest.clearAllMocks()
const { errors: errorObjects } = await mutate({
mutation: createContributionLink,
variables: {
...variables,
memo: '1234567890123456789212345678931234567894123456789512345678961234567897123456789812345678991234567890123456789012345678921234567893123456789412345678951234567896123456789712345678981234567899123456789012345678901234567892123456789312345678941234567895123456',
memo: '123456789012345678921234567893123456789412345678951234567896123456789712345678981234567899123456789012345678901234567892123456789312345678941234567895123456789612345678971234567898123456789912345678901234567890123456789212345678931234567894123456789512345612345678901234567892123456789312345678941234567895123456789612345678971234567898123456789912345678901234567890123456789212345678931234567894123456789512345678961234567897123456789812345678991234567890123456789012345678921234567893123456789412345678951234567',
},
})
expect(errorObjects).toMatchObject([
@ -441,7 +441,7 @@ describe('Contribution Links', () => {
{
property: 'memo',
constraints: {
maxLength: 'memo must be shorter than or equal to 255 characters',
maxLength: 'memo must be shorter than or equal to 512 characters',
},
},
],

View File

@ -229,14 +229,14 @@ describe('ContributionResolver', () => {
])
})
it('throws error when memo length greater than 255 chars', async () => {
it('throws error when memo length greater than 512 chars', async () => {
jest.clearAllMocks()
const date = new Date()
const { errors: errorObjects } = await mutate({
mutation: createContribution,
variables: {
amount: 100.0,
memo: 'Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test',
memo: 'Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test',
contributionDate: date.toString(),
},
})
@ -249,7 +249,7 @@ describe('ContributionResolver', () => {
{
property: 'memo',
constraints: {
maxLength: 'memo must be shorter than or equal to 255 characters',
maxLength: 'memo must be shorter than or equal to 512 characters',
},
},
],
@ -398,7 +398,7 @@ describe('ContributionResolver', () => {
})
})
describe('Memo length greater than 255 chars', () => {
describe('Memo length greater than 512 chars', () => {
it('throws error', async () => {
jest.clearAllMocks()
const date = new Date()
@ -407,7 +407,7 @@ describe('ContributionResolver', () => {
variables: {
contributionId: pendingContribution.data.createContribution.id,
amount: 100.0,
memo: 'Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test',
memo: 'Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test',
contributionDate: date.toString(),
},
})
@ -420,7 +420,7 @@ describe('ContributionResolver', () => {
{
property: 'memo',
constraints: {
maxLength: 'memo must be shorter than or equal to 255 characters',
maxLength: 'memo must be shorter than or equal to 512 characters',
},
},
],

View File

@ -187,7 +187,7 @@ describe('TransactionLinkResolver', () => {
variables: {
identifier: 'peter@lustig.de',
amount: 100,
memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test t',
memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test',
},
})
expect(errorObjects).toMatchObject([
@ -199,7 +199,7 @@ describe('TransactionLinkResolver', () => {
{
property: 'memo',
constraints: {
maxLength: 'memo must be shorter than or equal to 255 characters',
maxLength: 'memo must be shorter than or equal to 512 characters',
},
},
],

View File

@ -269,7 +269,7 @@ describe('send coins', () => {
recipientCommunityIdentifier: homeCom.communityUuid,
recipientIdentifier: 'peter@lustig.de',
amount: 100,
memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test t',
memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test',
},
})
expect(errorObjects).toMatchObject([
@ -281,7 +281,7 @@ describe('send coins', () => {
{
property: 'memo',
constraints: {
maxLength: 'memo must be shorter than or equal to 255 characters',
maxLength: 'memo must be shorter than or equal to 512 characters',
},
},
],

View File

@ -8,7 +8,7 @@ export const FULL_CREATION_AVAILABLE = [
]
export const CONTRIBUTIONLINK_NAME_MAX_CHARS = 100
export const CONTRIBUTIONLINK_NAME_MIN_CHARS = 5
export const MEMO_MAX_CHARS = 255
export const MEMO_MAX_CHARS = 512
export const MEMO_MIN_CHARS = 5
export const DEFAULT_PAGINATION_PAGE_SIZE = 25
export const FRONTEND_CONTRIBUTIONS_ITEM_ANCHOR_PREFIX = 'contributionListItem-'

View File

@ -92,7 +92,7 @@ export const GMS_ACTIVE = Joi.boolean()
.required()
export const GDT_ACTIVE = Joi.boolean()
.description('Flag to indicate if the GMS (Geographic Member Search) service is used.')
.description('Flag to indicate if the GDT (Gradido Transform) service is used.')
.default(false)
.required()

View File

@ -56,27 +56,32 @@ ENV PATH="/root/.bun/bin:${PATH}"
FROM bun-base as installer
COPY --chown=app:app . .
RUN bun install --filter database --production --no-cache --frozen-lockfile
##################################################################################
# Build Shared ###################################################################
##################################################################################
FROM installer as build-shared
RUN bun install --filter shared --no-cache --frozen-lockfile \
&& cd shared && yarn typecheck && yarn build
##################################################################################
# Build ##########################################################################
##################################################################################
FROM installer as build
RUN bun install --no-cache --frozen-lockfile \
&& cd shared && yarn build \
&& cd ../database && yarn build && yarn typecheck
RUN bun install --filter database --production --no-cache --frozen-lockfile
##################################################################################
# PRODUCTION IMAGE ###############################################################
##################################################################################
FROM base as production
COPY --chown=app:app --from=installer ${DOCKER_WORKDIR}/src ./src
COPY --chown=app:app --from=installer ${DOCKER_WORKDIR}/migration ./migration
COPY --chown=app:app --from=installer ${DOCKER_WORKDIR}/node_modules ./node_modules
COPY --chown=app:app --from=installer ${DOCKER_WORKDIR}/package.json ./package.json
COPY --chown=app:app --from=installer ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig.json
COPY --chown=app:app --from=build-shared ${DOCKER_WORKDIR}/shared/build ./shared/build
COPY --chown=app:app --from=build-shared ${DOCKER_WORKDIR}/shared/package.json ./shared/package.json
COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/database ./database
COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/package.json ./package.json
##################################################################################
# TEST UP ########################################################################
@ -84,7 +89,7 @@ COPY --chown=app:app --from=installer ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig
FROM production as up
# Run command
CMD /bin/sh -c "yarn up"
CMD /bin/sh -c "cd database && yarn up"
##################################################################################
# TEST RESET #####################################################################
@ -92,7 +97,7 @@ CMD /bin/sh -c "yarn up"
FROM production as reset
# Run command
CMD /bin/sh -c "yarn reset"
CMD /bin/sh -c "cd database && yarn reset"
##################################################################################
# TEST DOWN ######################################################################
@ -100,4 +105,4 @@ CMD /bin/sh -c "yarn reset"
FROM production as down
# Run command
CMD /bin/sh -c "yarn down"
CMD /bin/sh -c "cd database && yarn down"

View File

@ -0,0 +1,14 @@
/* MIGRATION TO ADD hiero topic id IN COMMUNITY TABLE
*
* This migration adds fields for the hiero topic id in the community.table
*/
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
'ALTER TABLE `communities` ADD COLUMN `hiero_topic_id` varchar(512) DEFAULT NULL AFTER `location`;',
)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn('ALTER TABLE `communities` DROP COLUMN `hiero_topic_id`;')
}

View File

@ -0,0 +1,20 @@
/* MIGRATION TO INCREASE memo TO 512 in all tables which have a memo field
*
* This migration increases the memo field in all tables which have a memo field to 512
*/
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn('ALTER TABLE `contributions` MODIFY COLUMN `memo` varchar(512) NOT NULL;')
await queryFn('ALTER TABLE `contribution_links` MODIFY COLUMN `memo` varchar(512) NOT NULL;')
await queryFn('ALTER TABLE `pending_transactions` MODIFY COLUMN `memo` varchar(512) NOT NULL;')
await queryFn('ALTER TABLE `transactions` MODIFY COLUMN `memo` varchar(512) NOT NULL;')
await queryFn('ALTER TABLE `transaction_links` MODIFY COLUMN `memo` varchar(512) NOT NULL;')
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn('ALTER TABLE `contributions` MODIFY COLUMN `memo` varchar(255) NOT NULL;')
await queryFn('ALTER TABLE `contribution_links` MODIFY COLUMN `memo` varchar(255) NOT NULL;')
await queryFn('ALTER TABLE `pending_transactions` MODIFY COLUMN `memo` varchar(255) NOT NULL;')
await queryFn('ALTER TABLE `transactions` MODIFY COLUMN `memo` varchar(255) NOT NULL;')
await queryFn('ALTER TABLE `transaction_links` MODIFY COLUMN `memo` varchar(255) NOT NULL;')
}

View File

@ -73,6 +73,9 @@ export class Community extends BaseEntity {
})
location: Geometry | null
@Column({ name: 'hiero_topic_id', type: 'varchar', length: 255, nullable: true })
hieroTopicId: string | null
@CreateDateColumn({
name: 'created_at',
type: 'datetime',

View File

@ -39,7 +39,7 @@ export class Contribution extends BaseEntity {
@Column({ type: 'datetime', nullable: false, name: 'contribution_date' })
contributionDate: Date
@Column({ type: 'varchar', length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
@Column({ type: 'varchar', length: 512, nullable: false, collation: 'utf8mb4_unicode_ci' })
memo: string
@Column({

View File

@ -10,7 +10,7 @@ export class ContributionLink extends BaseEntity {
@Column({ type: 'varchar', length: 100, nullable: false, collation: 'utf8mb4_unicode_ci' })
name: string
@Column({ type: 'varchar', length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
@Column({ type: 'varchar', length: 512, nullable: false, collation: 'utf8mb4_unicode_ci' })
memo: string
@Column({ name: 'valid_from', type: 'datetime', nullable: false })

View File

@ -71,7 +71,7 @@ export class PendingTransaction extends BaseEntity {
})
decayStart: Date | null
@Column({ type: 'varchar', length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
@Column({ type: 'varchar', length: 512, nullable: false, collation: 'utf8mb4_unicode_ci' })
memo: string
@Column({ name: 'creation_date', type: 'datetime', precision: 3, nullable: true, default: null })

View File

@ -70,7 +70,7 @@ export class Transaction extends BaseEntity {
})
decayStart: Date | null
@Column({ type: 'varchar', length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
@Column({ type: 'varchar', length: 512, nullable: false, collation: 'utf8mb4_unicode_ci' })
memo: string
@Column({ name: 'creation_date', type: 'datetime', precision: 3, nullable: true, default: null })

View File

@ -29,7 +29,7 @@ export class TransactionLink extends BaseEntity {
})
holdAvailableAmount: Decimal
@Column({ type: 'varchar', length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
@Column({ type: 'varchar', length: 512, nullable: false, collation: 'utf8mb4_unicode_ci' })
memo: string
@Column({ type: 'varchar', length: 24, nullable: false, collation: 'utf8mb4_unicode_ci' })

View File

@ -49,15 +49,16 @@ export const findUserByIdentifier = async (
const user = userContact.user
user.emailContact = userContact
return user
}
}
} else if (aliasSchema.safeParse(identifier).success) {
return await DbUser.findOne({
where: { alias: identifier, community: communityWhere },
relations: ['emailContact', 'community'],
})
} else {
// should don't happen often, so we create only in the rare case a logger for it
getLogger(`${LOG4JS_QUERIES_CATEGORY_NAME}.user.findUserByIdentifier`).warn('Unknown identifier type', identifier)
}
// should don't happen often, so we create only in the rare case a logger for it
getLogger(`${LOG4JS_QUERIES_CATEGORY_NAME}.user.findUserByIdentifier`).warn('Unknown identifier type', identifier)
return null
}

1907
e2e-tests/bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "typescript",
"devDependencies": {
"@playwright/test": "^1.53.1",
"@types/node": "^24.0.7",
},
},
},
"packages": {
"@playwright/test": ["@playwright/test@1.54.2", "", { "dependencies": { "playwright": "1.54.2" }, "bin": { "playwright": "cli.js" } }, "sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA=="],
"@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="],
"fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
"playwright": ["playwright@1.54.2", "", { "dependencies": { "playwright-core": "1.54.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw=="],
"playwright-core": ["playwright-core@1.54.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA=="],
"undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
}
}

View File

@ -161,7 +161,7 @@ const validationSchema = computed(() => {
})), // date cannot be in the future
memo: string()
.min(5, ({ min }) => ({ key: 'form.validation.contributionMemo.min', values: { min } }))
.max(255, ({ max }) => ({ key: 'form.validation.contributionMemo.max', values: { max } }))
.max(512, ({ max }) => ({ key: 'form.validation.contributionMemo.max', values: { max } }))
.required('form.validation.contributionMemo.required'),
hours: number()
.typeError({ key: 'form.validation.hours.typeError', values: { min: 0.01, max: maxHours } })

View File

@ -40,11 +40,13 @@
<BRow>
<BCol cols="12" lg="5">
<div>
<gdd-amount
:balance="balance"
:show-status="false"
:badge-show="false"
/>
<router-link to="transactions">
<gdd-amount
:balance="balance"
:show-status="false"
:badge-show="false"
/>
</router-link>
</div>
</BCol>
<BCol cols="12" lg="7">
@ -79,9 +81,7 @@
<BRow>
<BCol cols="12" lg="6">
<div>
<router-link to="transactions">
<gdd-amount :balance="balance" :show-status="true" />
</router-link>
<gdd-amount :balance="balance" :show-status="true" />
</div>
</BCol>
<BCol cols="12" lg="6">
@ -104,13 +104,11 @@
</BCol>
<BCol cols="12" lg="6">
<div>
<router-link to="gdt">
<gdt-amount
:badge="true"
:show-status="true"
:gdt-balance="GdtBalance"
/>
</router-link>
<gdt-amount
:badge="true"
:show-status="true"
:gdt-balance="GdtBalance"
/>
</div>
</BCol>
</BRow>

View File

@ -24,7 +24,7 @@ export const translateYupErrorString = (error, t) => {
export const memo = string()
.required('form.validation.memo.required')
.min(5, ({ min }) => ({ key: 'form.validation.memo.min', values: { min } }))
.max(255, ({ max }) => ({ key: 'form.validation.memo.max', values: { max } }))
.max(512, ({ max }) => ({ key: 'form.validation.memo.max', values: { max } }))
export const identifier = string()
.required('form.validation.identifier.required')