Skip to content

Commit 570a349

Browse files
feat(global): DOMA-7425 B2C-miniapps MVP πŸ“±πŸ‘¨β€πŸ’»πŸ˜Ž (#4092)
* feat(condo): DOMA-7799 Remove unused field from B2CApp * feat(dev-api): DOMA-7425 added B2CApp model * feat(dev-api): DOMA-7425 added B2CAppBuild model * feat(dev-api): DOMA-7425 added B2CAppProperty model * feat(dev-portal): DOMA-7425 make dev-portal mobile-friendly * feat(dev-portal): DOMA-7425 make dev-portal mobile-friendly * feat(dev-portal): DOMA-7425 tmp commmit * fix(ui): DOMA-7425 added inherit title type * feat(ui): DOMA-7425 mobile first layouts * fix(ui): DOMA-7425 card classname fixed * fix(dev-portal): DOMA-7425 tmp commmit * feat(dev-portal): DOMA-7425 apps view added * feat(dev-portal): DOMA-7425 apps basic creation added * feat(registry): DOMA-7425 restore SSR in /apps page * feat(dev-portal): DOMA-7425 app list and creating is finished * feat(dev-portal): DOMA-7425 name edit done * feat(dev-portal): DOMA-7425 tmp commit (images) * feat(dev-portal): DOMA-7425 apps info finished * feat(dev-portal): DOMA-7425 remove client antd warning * feat(dev-portal): DOMA-7425 tmp commit (builds) * feat(dev-portal): DOMA-7425 translations restructured * feat(dev-portal): DOMA-7425 added build creation * feat(dev-portal): DOMA-7425 added builds view * fix(dev-portal): DOMA-7425 minor visual bugs fixed * fix(dev-portal): DOMA-7425 remove B2CAppProperty model * feat(dev-api): DOMA-7698 prepare.js to create bots * feat(dev-portal): DOMA-7425 publish form added * feat(dev-portal): DOMA-7425 added developer field * feat(dev-portal): DOMA-7425 form publish mutation signature * feat(dev-api): DOMA-7425 logo re-upload demo * feat(dev-api): DOMA-7425 add publish API * feat(dev-portal): DOMA-7425 connect front-end publishing with mutation * feat(dev-portal): DOMA-7425 added publish tests * feat(dev-portal): DOMA-7425 temp commit * feat(dev-portal): DOMA-7425 build upload cache invalidate * feat(dev-portal): DOMA-7425 minor UI improvements * feat(dev-api): DOMA-7425 added logging to publish job * feat(dev-api): DOMA-7425 added B2CAppPublishRequest model * feat(dev-api): DOMA-7425 added prod publish restrictions * feat(dev-portal): DOMA-7425 added prod publish restrictions * feat(dev-api): DOMA-7425 added webhook models * feat(dev-api): DOMA-7425 added worker * feat(dev-portal): DOMA-7425 added loading states and s3 support * fig(dev-api): DOMA-7425 fixed sonar issues * ci(global): DOMA-7425 check paths trigger * ci(global): DOMA-7425 check paths trigger * ci(global): DOMA-7425 fix job trigger * ci(global): DOMA-7425 added prepare with docker * ci(global): DOMA-7425 exclude checks when prepare in CI * ci(global): DOMA-7425 swap docker-compose db management to pg NPM * ci(global): DOMA-7425 add yarn prefix to turbo * ci(keystone): DOMA-7425 add internal GQLError in production info * ci(global): DOMA-7425 added app-waiter * ci(dev-api): DOMA-7425 added dev-api tests job * ci(dev-api): DOMA-7425 fix start command * ci(dev-api): DOMA-7425 single command run * ci(dev-api): DOMA-7425 fix tests
1 parent 06671f8 commit 570a349

File tree

133 files changed

+14922
-1314
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+14922
-1314
lines changed

Diff for: β€Ž.github/workflows/nodejs.condo.ci.yml

+64-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ on:
1111

1212
env:
1313
DOCKER_IMAGE_FULL: ${{ secrets.DOCKER_REGISTRY }}/condo/condo-image:${{ github.sha }}
14+
PG_IMAGE_FULL: ${{ secrets.DOCKER_REGISTRY }}/doma/utils/postgres:13.2
15+
REDIS_IMAGE_FULL: ${{ secrets.DOCKER_REGISTRY }}/doma/utils/redis:6.2
1416

1517
jobs:
1618
build-image:
@@ -48,7 +50,7 @@ jobs:
4850
- name: Login to cloud registry
4951
uses: docker/login-action@v3
5052
with:
51-
registry: swr.ru-moscow-1.hc.sbercloud.ru
53+
registry: ${{ secrets.DOCKER_REGISTRY }}
5254
username: ${{ secrets.SBERCLOUD_CR_USERNAME }}
5355
password: ${{ secrets.SBERCLOUD_CR_PASSWORD }}
5456
- name: Build repo image
@@ -78,6 +80,29 @@ jobs:
7880
./docker-logs
7981
retention-days: 2
8082

83+
# SRC: https://how.wtf/run-workflow-step-or-job-based-on-file-changes-github-actions.html
84+
detect-changes:
85+
name: Define job list based on changed files
86+
runs-on: ubuntu-latest
87+
outputs:
88+
dev-api: ${{ steps.detect-changes.outputs.dev-api }}
89+
steps:
90+
- name: Checkout code with submodules
91+
uses: actions/checkout@v3
92+
with:
93+
fetch-depth: 0
94+
submodules: recursive
95+
ssh-key: ${{ secrets.SSH_DOCK_SERVER_PRIVATE_KEY }}
96+
- name: Scan changed files
97+
uses: dorny/paths-filter@v2
98+
id: detect-changes
99+
with:
100+
filters: |
101+
dev-api:
102+
- 'apps/dev-api/**'
103+
- 'apps/condo/domains/miniapp/**'
104+
105+
81106
lint:
82107
name: Lint source code
83108
runs-on: ubuntu-latest
@@ -120,3 +145,41 @@ jobs:
120145
# Fetch project source with GitHub Actions Checkout.
121146
- uses: actions/checkout@v3
122147
- run: ./bin/run-semgrep.sh -a
148+
149+
run-dev-api-tests:
150+
name: DEV-API Tests
151+
runs-on: ubuntu-latest
152+
needs:
153+
- detect-changes
154+
- build-image
155+
if: ${{ needs.detect-changes.outputs.dev-api == 'true' }}
156+
steps:
157+
- name: Login to cloud registry
158+
uses: docker/login-action@v3
159+
with:
160+
registry: ${{ secrets.DOCKER_REGISTRY }}
161+
username: ${{ secrets.SBERCLOUD_CR_USERNAME }}
162+
password: ${{ secrets.SBERCLOUD_CR_PASSWORD }}
163+
- name: Setup PG db
164+
run: |
165+
docker run -e POSTGRES_USER=$POSTGRES_USER -e POSTGRES_PASSWORD=$POSTGRES_PASSWORD -e POSTGRES_DB=$POSTGRES_DB -p="127.0.0.1:5432:5432" -d ${{ env.PG_IMAGE_FULL }}
166+
env:
167+
POSTGRES_USER: postgres
168+
POSTGRES_PASSWORD: postgres
169+
POSTGRES_DB: main
170+
- name: Setup Redis db
171+
run: |
172+
docker run -p="127.0.0.1:6379:6379" -d ${{ env.REDIS_IMAGE_FULL }}
173+
- name: Run condo container with daemon
174+
run: |
175+
docker run --network="host" --name condo-container -dit ${{ env.DOCKER_IMAGE_FULL }} sh
176+
- name: Prepare apps
177+
run: |
178+
docker exec condo-container node bin/prepare.js -f condo dev-api
179+
- name: Run apps and tests
180+
run: |
181+
docker exec condo-container sh -c "(\
182+
yarn workspace @app/condo start & \
183+
yarn workspace @app/dev-api start & \
184+
node bin/wait-apps-apis.js -f condo dev-api) && \
185+
yarn workspace @app/dev-api test"

Diff for: β€Ž.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ codegen.yaml
1919

2020
schema.graphql
2121
!apps/condo/schema.graphql
22+
!apps/dev-api/schema.graphql
2223

2324
# yarn + npm
2425
yarn-error.log

Diff for: β€Ž.stylelintrc.json

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
"stylelint-config-rational-order"
55
],
66
"rules": {
7+
"selector-pseudo-class-no-unknown": [
8+
true,
9+
{
10+
"ignorePseudoClasses": ["global"]
11+
}
12+
],
713
"at-rule-no-unknown": [true, {
814
"ignoreAtRules": ["tailwind"]
915
}],

Diff for: β€Žapps/condo/bin/create-user.js

+53-17
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const path = require('path')
22

33
const { getRandomString, prepareKeystoneExpressApp } = require('@open-condo/keystone/prepareKeystoneApp')
44

5-
const { User } = require('@condo/domains/user/utils/serverSchema')
5+
const { User, UserRightsSet } = require('@condo/domains/user/utils/serverSchema')
66

77
function getJson (data) {
88
try {
@@ -13,31 +13,67 @@ function getJson (data) {
1313
}
1414

1515
async function main (args) {
16-
const [email, options] = args
17-
let optionsJson = getJson(options)
18-
if (!email || !email.includes('@')) throw new Error('use: create-user <email> [<options>]')
19-
if (options && !optionsJson) throw new Error('<options> argument should be a valid json')
20-
const json = optionsJson || {}
16+
const [email, userOptions, rightsSet] = args
17+
if (!email || !email.includes('@')) throw new Error('use: create-user <email> [<options>] [<rightsSet>]')
18+
const parsedUserOptions = getJson(userOptions)
19+
if (userOptions && !parsedUserOptions) throw new Error('<options> argument should be a valid json object')
20+
const parsedRightsSet = getJson(rightsSet)
21+
if (rightsSet && !parsedRightsSet) throw new Error('<rightsSet> argument should be a valid json object')
22+
23+
const userPayload = parsedUserOptions || {}
2124

2225
const { keystone: context } = await prepareKeystoneExpressApp(path.resolve('./index.js'), { excludeApps: ['NextApp', 'AdminUIApp'] })
2326

2427
console.info(`EMAIL: ${email}`)
25-
const user = await User.getOne(context, { email })
26-
if (!json.dv) json.dv = 1
27-
if (!json.sender) json.sender = { 'dv': 1, 'fingerprint': 'create-user-script' }
28-
if (!user) {
29-
if (!json.password) {
30-
json.password = getRandomString()
31-
console.info(`PASSWORD: ${json.password}`)
28+
const existingUser = await User.getOne(context, { email })
29+
userPayload.dv ??= 1
30+
userPayload.sender ??= { 'dv': 1, 'fingerprint': 'create-user-script' }
31+
let rightSetId
32+
let userId
33+
34+
if (!existingUser) {
35+
if (!userPayload.password) {
36+
userPayload.password = getRandomString()
37+
console.info(`PASSWORD: ${userPayload.password}`)
3238
}
33-
if (!json.name) json.name = email.split('@')[0]
34-
await User.create(context, {
35-
email, ...json,
39+
userPayload.name ??= email.split('@')[0]
40+
const user = await User.create(context, {
41+
email,
42+
...userPayload,
3643
})
3744
console.info('User created!')
45+
userId = user.id
46+
if (user.rightsSet) {
47+
rightSetId = user.rightsSet.id
48+
}
3849
} else {
39-
await User.update(context, user.id, json)
50+
const user = await User.update(context, existingUser.id, userPayload)
4051
console.info('User updated!')
52+
userId = existingUser.id
53+
if (user.rightsSet) {
54+
rightSetId = user.rightsSet.id
55+
}
56+
}
57+
58+
if (parsedRightsSet) {
59+
const rightSetPayload = {
60+
dv: 1,
61+
sender: { dv: 1, fingerprint: 'create-user-script' },
62+
...parsedRightsSet,
63+
}
64+
if (rightSetId) {
65+
await UserRightsSet.update(context, rightSetId, rightSetPayload)
66+
console.info('UserRightsSet updated!')
67+
} else {
68+
await User.update(context, userId, {
69+
dv: 1,
70+
sender: { dv: 1, fingerprint: 'create-user-script' },
71+
rightsSet: {
72+
create: rightSetPayload,
73+
},
74+
})
75+
console.info('UserRightsSet created and linked!')
76+
}
4177
}
4278
}
4379

Diff for: β€Žapps/dev-api/.env.example

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
SMS_WHITE_LIST='{"+79990001234": "1234"}'
22
IP_WHITE_LIST='["::ffff:127.0.0.1"]'
33
SMS_PROVIDER=fake
4-
CONDO_DEV_BOT_CONFIG='{"apiUrl": "http://localhost:4004", "email": "[email protected]", "password": "your-secret-pass" }'
5-
CONDO_PROD_BOT_CONFIG='{"apiUrl": "http://localhost:4004", "email": "[email protected]", "password": "your-secret-pass" }'
4+
FILE_FIELD_ADAPTER=local

Diff for: β€Žapps/dev-api/bin/prepare.js

+50-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,60 @@
11
const path = require('path')
22

3-
const { prepareAppEnvLocalAdminUsers } = require('@open-condo/cli')
3+
const { faker } = require('@faker-js/faker')
4+
5+
const { prepareAppEnvLocalAdminUsers, getAppEnvValue, updateAppEnvFile, safeExec } = require('@open-condo/cli')
46

57
const APP_NAME = path.basename(path.resolve(__dirname, '..'))
8+
const BOT_RIGHTS_SET = {
9+
name: '[DEV-API] Service bot permissions',
10+
canReadB2BApps: true,
11+
canReadB2BAppAccessRights: true,
12+
canReadB2BAppAccessRightSets: true,
13+
canReadB2BAppPermissions: true,
14+
canReadB2BAppNewsSharingConfigs: true,
15+
canReadB2CApps: true,
16+
canReadB2CAppAccessRights: true,
17+
canReadB2CAppBuilds: true,
18+
canReadB2CAppProperties: true,
19+
20+
canManageB2BApps: true,
21+
canManageB2BAppAccessRights: true,
22+
canManageB2BAppAccessRightSets: true,
23+
canManageB2BAppPermissions: true,
24+
canManageB2BAppNewsSharingConfigs: true,
25+
canManageB2CApps: true,
26+
canManageB2CAppAccessRights: true,
27+
canManageB2CAppBuilds: true,
28+
canManageB2CAppProperties: true,
29+
30+
canExecuteRegisterNewServiceUser: true,
31+
canExecuteSendMessage: true,
32+
}
633

734
async function main () {
835
await prepareAppEnvLocalAdminUsers(APP_NAME, 'phone')
36+
37+
const condoUrl = await getAppEnvValue(APP_NAME, 'CONDO_DOMAIN')
38+
const devBotEnvValue = await getAppEnvValue(APP_NAME, 'CONDO_DEV_BOT_CONFIG')
39+
const devBotConfig = devBotEnvValue ? JSON.parse(devBotEnvValue) : {
40+
41+
password: faker.internet.password(16),
42+
}
43+
devBotConfig.apiUrl = `${condoUrl}/admin/api`
44+
await updateAppEnvFile(APP_NAME, 'CONDO_DEV_BOT_CONFIG', JSON.stringify(devBotConfig))
45+
46+
const prodBotEnvValue = await getAppEnvValue(APP_NAME, 'CONDO_PROD_BOT_CONFIG')
47+
const prodBotConfig = prodBotEnvValue ? JSON.parse(prodBotEnvValue) : {
48+
49+
password: faker.internet.password(16),
50+
}
51+
prodBotConfig.apiUrl = `${condoUrl}/admin/api`
52+
await updateAppEnvFile(APP_NAME, 'CONDO_PROD_BOT_CONFIG', JSON.stringify(prodBotConfig))
53+
54+
const devBotOptions = { type: 'service', password: devBotConfig.password, name: '[DEV-API] Dev bot' }
55+
const prodBotOptions = { type: 'service', password: prodBotConfig.password, name: '[DEV-API] Prod bot' }
56+
await safeExec(`yarn workspace @app/condo node bin/create-user.js ${devBotConfig.email} '${JSON.stringify(devBotOptions)}' '${JSON.stringify(BOT_RIGHTS_SET)}'`)
57+
await safeExec(`yarn workspace @app/condo node bin/create-user.js ${prodBotConfig.email} '${JSON.stringify(prodBotOptions)}' '${JSON.stringify(BOT_RIGHTS_SET)}'`)
958
}
1059

1160
main().then(() => {

Diff for: β€Žapps/dev-api/domains/common/constants/common.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const REMOTE_SYSTEM = 'dev-api'
2+
3+
module.exports = {
4+
REMOTE_SYSTEM,
5+
}

Diff for: β€Žapps/dev-api/domains/common/constants/errors.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const INVALID_MIMETYPE = 'INVALID_MIMETYPE'
2+
const MULTIPLE_FOUND = 'MULTIPLE_FOUND'
3+
4+
module.exports = {
5+
INVALID_MIMETYPE,
6+
MULTIPLE_FOUND,
7+
}

Diff for: β€Žapps/dev-api/domains/common/utils/files.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const get = require('lodash/get')
2+
3+
const { GQLError, GQLErrorCode: { BAD_USER_INPUT } } = require('@open-condo/keystone/errors')
4+
5+
6+
// TODO: Move everything to @open-condo/keystone or somewhere outside of condo to avoid possible side-effects
7+
const FileAdapter = require('@condo/domains/common/utils/fileAdapter')
8+
const { getFileMetaAfterChange, makeFileAdapterMiddleware } = require('@condo/domains/common/utils/fileAdapter')
9+
const { INVALID_MIMETYPE } = require('@dev-api/domains/common/constants/errors')
10+
11+
const ERRORS = {
12+
INVALID_MIMETYPE: {
13+
code: BAD_USER_INPUT,
14+
type: INVALID_MIMETYPE,
15+
message: 'Attached file have invalid mimetype',
16+
messageForUser: 'errors.INVALID_MIMETYPE.message',
17+
},
18+
}
19+
20+
function getMimeTypesValidator ({ allowedTypes }) {
21+
return function validateInput ({ resolvedData, fieldPath, context }) {
22+
const mimeType = get(resolvedData, [fieldPath, 'mimetype'])
23+
if (!allowedTypes.includes(mimeType)) {
24+
throw new GQLError({
25+
...ERRORS.INVALID_MIMETYPE,
26+
messageInterpolation: { types: allowedTypes },
27+
}, context)
28+
}
29+
}
30+
}
31+
32+
module.exports = {
33+
FileAdapter,
34+
getFileMetaAfterChange,
35+
makeFileAdapterMiddleware,
36+
getMimeTypesValidator,
37+
}

Diff for: β€Žapps/dev-api/domains/common/utils/serverClients.js

+30
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ const get = require('lodash/get')
22

33
const { ApolloServerClient } = require('@open-condo/apollo-server-client')
44
const conf = require('@open-condo/config')
5+
const { GQLError, GQLErrorCode: { INTERNAL_ERROR } } = require('@open-condo/keystone/errors')
56

7+
const { REMOTE_SYSTEM } = require('@dev-api/domains/common/constants/common')
8+
const { MULTIPLE_FOUND } = require('@dev-api/domains/common/constants/errors')
69
const { DEFAULT_LOCALE } = require('@dev-api/domains/common/constants/locales')
710
const { SEND_MESSAGE_MUTATION } = require('@dev-api/domains/common/gql')
811

@@ -19,6 +22,15 @@ function _getClientRequisites (config) {
1922
}
2023
}
2124

25+
const ERRORS = {
26+
MULTIPLE_FOUND: {
27+
code: INTERNAL_ERROR,
28+
type: MULTIPLE_FOUND,
29+
message: 'Unable to determine the object to update because multiple objects were found for the specified importID and exportId',
30+
messageForUser: 'errors.MULTIPLE_FOUND.message',
31+
},
32+
}
33+
2234

2335
class CondoClient extends ApolloServerClient {
2436
constructor ({ endpoint, authRequisites, opts }) {
@@ -42,6 +54,24 @@ class CondoClient extends ApolloServerClient {
4254
},
4355
})
4456
}
57+
58+
async findExportedModel ({ modelGql, exportId, id, context }) {
59+
const searchConditions = []
60+
if (exportId) searchConditions.push({ id: exportId })
61+
searchConditions.push({ AND: [{ importId: id, importRemoteSystem: REMOTE_SYSTEM }] })
62+
const objs = await this.getModels({
63+
modelGql,
64+
first: 2,
65+
where: {
66+
OR: searchConditions,
67+
},
68+
})
69+
if (objs.length > 1) {
70+
throw new GQLError(ERRORS.MULTIPLE_FOUND, context)
71+
}
72+
73+
return objs.length ? objs[0] : null
74+
}
4575
}
4676

4777
const developmentClient = new CondoClient({

Diff for: β€Žapps/dev-api/domains/condo/gql.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const { generateGqlQueries } = require('@open-condo/codegen/generate.gql')
2+
3+
const CondoB2CAppGql = generateGqlQueries('B2CApp', '{ id }')
4+
const CondoB2CAppBuildGql = generateGqlQueries('B2CAppBuild', '{ id }')
5+
6+
module.exports = {
7+
CondoB2CAppGql,
8+
CondoB2CAppBuildGql,
9+
}

0 commit comments

Comments
Β (0)