@things-factory/figure-service 10.1.159 → 10.1.161
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/service/figure/figure-namespace-resolver.d.ts +10 -0
- package/dist-server/service/figure/figure-namespace-resolver.js +65 -0
- package/dist-server/service/figure/figure-namespace-resolver.js.map +1 -0
- package/dist-server/service/figure/figure-namespace.d.ts +16 -0
- package/dist-server/service/figure/figure-namespace.js +41 -5
- package/dist-server/service/figure/figure-namespace.js.map +1 -1
- package/dist-server/service/figure/index.d.ts +2 -1
- package/dist-server/service/figure/index.js +2 -1
- package/dist-server/service/figure/index.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/server/service/figure/figure-namespace-db.test.ts +29 -0
- package/server/service/figure/figure-namespace-resolver.ts +41 -0
- package/server/service/figure/figure-namespace.ts +59 -6
- package/server/service/figure/index.ts +2 -1
- package/translations/en.json +1 -0
- package/translations/es.json +1 -0
- package/translations/ja.json +1 -0
- package/translations/ko.json +1 -0
- package/translations/ms.json +1 -0
- package/translations/zh.json +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-service",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.161",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -38,5 +38,5 @@
|
|
|
38
38
|
"jszip": "^3.10.1",
|
|
39
39
|
"koa-router": "^7.4.0"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "d32fe0614ea40572787acd94e307cc18a681ab38"
|
|
42
42
|
}
|
|
@@ -130,6 +130,35 @@ describe('the namespace a figure is created under', () => {
|
|
|
130
130
|
})
|
|
131
131
|
})
|
|
132
132
|
|
|
133
|
+
describe('the namespace setting, as the setting screen reads and writes it', () => {
|
|
134
|
+
const resolver = () => new (loadCompiled('service/figure/figure-namespace-resolver').FigureNamespaceResolver)()
|
|
135
|
+
|
|
136
|
+
it('reads the subdomain when nothing is written, and is not fixed before a figure is made', async () => {
|
|
137
|
+
const { domain, context } = await domainWith(null)
|
|
138
|
+
expect(await resolver().figureNamespace(context)).toEqual({ namespace: domain.subdomain, written: '', subdomain: domain.subdomain, fixed: false })
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('writes a namespace, and empty goes back to the subdomain; a new figure takes what it says', async () => {
|
|
142
|
+
const { domain, context } = await domainWith(null)
|
|
143
|
+
expect(await resolver().setFigureNamespace(' hatiolab.com ', context)).toMatchObject({ namespace: 'hatiolab.com', written: 'hatiolab.com', fixed: false })
|
|
144
|
+
expect(await resolver().setFigureNamespace('', context)).toMatchObject({ namespace: domain.subdomain, written: '', fixed: false })
|
|
145
|
+
await resolver().setFigureNamespace('hatiolab.com', context)
|
|
146
|
+
const figure = await new FigureMutation().saveFigure(releasable(), context, undefined, undefined, creationId(), undefined, 'conveyor')
|
|
147
|
+
expect(figure.namespace).toBe('hatiolab.com')
|
|
148
|
+
expect(await resolver().figureNamespace(context)).toMatchObject({ namespace: 'hatiolab.com', fixed: true })
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('refuses what is not a namespace, and another namespace once figures carry one; the setting is left as it was', async () => {
|
|
152
|
+
const { context } = await domainWith('hatiolab.com')
|
|
153
|
+
await expect(resolver().setFigureNamespace('Hatiolab.COM', context)).rejects.toMatchObject({ code: 'figure-namespace-invalid' })
|
|
154
|
+
await new FigureMutation().saveFigure(releasable(), context, undefined, undefined, creationId(), undefined, 'conveyor')
|
|
155
|
+
await expect(resolver().setFigureNamespace('other.com', context)).rejects.toMatchObject({ code: 'figure-namespace-changed' })
|
|
156
|
+
expect(await resolver().figureNamespace(context)).toMatchObject({ namespace: 'hatiolab.com', written: 'hatiolab.com', fixed: true })
|
|
157
|
+
/* The same one again is not a change. */
|
|
158
|
+
await expect(resolver().setFigureNamespace('hatiolab.com', context)).resolves.toMatchObject({ namespace: 'hatiolab.com' })
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
133
162
|
describe('figureBlueprints answers each full name, an item or a refusal', () => {
|
|
134
163
|
it('gives released figures by full name and refuses the rest by cause, none taking another away', async () => {
|
|
135
164
|
const { context } = await domainWith('hatiolab.com')
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Arg, Ctx, Directive, Field, Mutation, ObjectType, Query, Resolver, UseMiddleware } from 'type-graphql'
|
|
2
|
+
|
|
3
|
+
import { getDataSource, SpeakRefusals } from '@things-factory/shell'
|
|
4
|
+
|
|
5
|
+
import { figureNamespaceStateOf, setFigureNamespace } from './figure-namespace.js'
|
|
6
|
+
|
|
7
|
+
@ObjectType({ description: "A domain's figure namespace, as the setting screen shows it (ADR-0114)." })
|
|
8
|
+
export class FigureNamespace {
|
|
9
|
+
@Field({ nullable: true, description: 'The namespace new figures are created under; null when neither the setting nor the subdomain is one.' })
|
|
10
|
+
namespace?: string
|
|
11
|
+
|
|
12
|
+
@Field({ description: 'The figure-namespace setting as written; empty when the subdomain is used.' })
|
|
13
|
+
written: string
|
|
14
|
+
|
|
15
|
+
@Field({ description: "The domain's subdomain, used when the setting is empty." })
|
|
16
|
+
subdomain: string
|
|
17
|
+
|
|
18
|
+
@Field({ description: "True once the domain's figures carry the namespace: it cannot be set any more." })
|
|
19
|
+
fixed: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Resolver()
|
|
23
|
+
@UseMiddleware(SpeakRefusals)
|
|
24
|
+
export class FigureNamespaceResolver {
|
|
25
|
+
@Directive('@privilege(category: "figure", privilege: "query", domainOwnerGranted: true)')
|
|
26
|
+
@Query(returns => FigureNamespace, { description: "The domain's figure namespace and whether it can still be set." })
|
|
27
|
+
async figureNamespace(@Ctx() context: ResolverContext): Promise<FigureNamespace> {
|
|
28
|
+
const { domain, tx } = context.state
|
|
29
|
+
return (await figureNamespaceStateOf(tx ?? getDataSource().manager, domain)) as FigureNamespace
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Directive('@transaction')
|
|
33
|
+
@Directive('@privilege(category: "setting", privilege: "mutation", domainOwnerGranted: true)')
|
|
34
|
+
@Mutation(returns => FigureNamespace, {
|
|
35
|
+
description: "Sets the domain's figure namespace. Empty takes the subdomain. Refused when it is not a namespace, or when the domain's figures already carry another."
|
|
36
|
+
})
|
|
37
|
+
async setFigureNamespace(@Arg('namespace') namespace: string, @Ctx() context: ResolverContext): Promise<FigureNamespace> {
|
|
38
|
+
const { domain, tx } = context.state
|
|
39
|
+
return (await setFigureNamespace(tx, domain, namespace)) as FigureNamespace
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EntityManager, Not } from 'typeorm'
|
|
1
|
+
import { EntityManager, IsNull, Not } from 'typeorm'
|
|
2
2
|
|
|
3
3
|
import { isFigureNamespace } from '@hatiolab/figure-model'
|
|
4
4
|
import { Domain, Refusal } from '@things-factory/shell'
|
|
@@ -19,11 +19,7 @@ export const FIGURE_NAMESPACE_SETTING = 'figure-namespace'
|
|
|
19
19
|
* from the namespace this domain's figures already carry is refused, so editing the setting cannot rename anything.
|
|
20
20
|
*/
|
|
21
21
|
export async function namespaceForNewFigure(tx: EntityManager, domain: Domain): Promise<string> {
|
|
22
|
-
const
|
|
23
|
-
where: { domain: { id: domain.id }, name: FIGURE_NAMESPACE_SETTING }
|
|
24
|
-
})
|
|
25
|
-
const written = setting?.value?.trim() ?? ''
|
|
26
|
-
const namespace = written || (domain.subdomain ?? '').trim()
|
|
22
|
+
const namespace = await chosenNamespace(tx, domain)
|
|
27
23
|
if (!isFigureNamespace(namespace)) {
|
|
28
24
|
throw new Refusal(
|
|
29
25
|
'figure-namespace-missing',
|
|
@@ -60,3 +56,60 @@ export function namespaceOf(figure: Figure): string {
|
|
|
60
56
|
}
|
|
61
57
|
return figure.namespace
|
|
62
58
|
}
|
|
59
|
+
|
|
60
|
+
/** What the setting says, or the subdomain when it says nothing: the namespace new figures are created under. */
|
|
61
|
+
async function chosenNamespace(tx: EntityManager, domain: Domain): Promise<string> {
|
|
62
|
+
return (await writtenNamespace(tx, domain)) || (domain.subdomain ?? '').trim()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function writtenNamespace(tx: EntityManager, domain: Domain): Promise<string> {
|
|
66
|
+
const setting = await tx.getRepository(Setting).findOne({
|
|
67
|
+
where: { domain: { id: domain.id }, name: FIGURE_NAMESPACE_SETTING }
|
|
68
|
+
})
|
|
69
|
+
return setting?.value?.trim() ?? ''
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A domain's figure namespace as the setting screen shows it. */
|
|
73
|
+
export interface FigureNamespaceState {
|
|
74
|
+
/** What new figures get, or null when neither the setting nor the subdomain is a namespace. */
|
|
75
|
+
namespace: string | null
|
|
76
|
+
/** The setting as written; empty when the subdomain is used. */
|
|
77
|
+
written: string
|
|
78
|
+
subdomain: string
|
|
79
|
+
/** The domain's figures already carry the namespace, so it cannot be set any more. */
|
|
80
|
+
fixed: boolean
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function figureNamespaceStateOf(tx: EntityManager, domain: Domain): Promise<FigureNamespaceState> {
|
|
84
|
+
const written = await writtenNamespace(tx, domain)
|
|
85
|
+
const subdomain = (domain.subdomain ?? '').trim()
|
|
86
|
+
const carried = await tx.getRepository(Figure).findOne({ where: { domain: { id: domain.id }, namespace: Not(IsNull()) } })
|
|
87
|
+
const chosen = written || subdomain
|
|
88
|
+
return {
|
|
89
|
+
namespace: carried?.namespace ?? (isFigureNamespace(chosen) ? chosen : null),
|
|
90
|
+
written,
|
|
91
|
+
subdomain,
|
|
92
|
+
fixed: !!carried?.namespace
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Writes the domain's figure namespace (ADR-0114). Empty takes the subdomain. Refused when the value, or the subdomain
|
|
98
|
+
* it falls back to, is not a namespace, and when the domain's figures already carry another one.
|
|
99
|
+
*/
|
|
100
|
+
export async function setFigureNamespace(tx: EntityManager, domain: Domain, value: string): Promise<FigureNamespaceState> {
|
|
101
|
+
const written = value.trim()
|
|
102
|
+
const namespace = written || (domain.subdomain ?? '').trim()
|
|
103
|
+
if (!isFigureNamespace(namespace)) {
|
|
104
|
+
throw new Refusal(
|
|
105
|
+
'figure-namespace-invalid',
|
|
106
|
+
{ value: namespace },
|
|
107
|
+
'{value} is not a figure namespace. use lower-case letters, digits, dots and hyphens, for example hatiolab.com.'
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
await assertNamespaceUnchanged(tx, domain, namespace)
|
|
111
|
+
const repository = tx.getRepository(Setting)
|
|
112
|
+
const setting = await repository.findOne({ where: { domain: { id: domain.id }, name: FIGURE_NAMESPACE_SETTING } })
|
|
113
|
+
await repository.save({ ...setting, domain, name: FIGURE_NAMESPACE_SETTING, category: 'figure', value: written })
|
|
114
|
+
return figureNamespaceStateOf(tx, domain)
|
|
115
|
+
}
|
|
@@ -4,8 +4,9 @@ import { FigureReplacedDraft } from './figure-replaced-draft.js'
|
|
|
4
4
|
import { FigureProposalRecord } from './figure-proposal-record.js'
|
|
5
5
|
import { FigureQuery } from './figure-query.js'
|
|
6
6
|
import { FigureMutation } from './figure-mutation.js'
|
|
7
|
+
import { FigureNamespaceResolver } from './figure-namespace-resolver.js'
|
|
7
8
|
import { FigureHistoryEntitySubscriber } from './event-subscriber.js'
|
|
8
9
|
|
|
9
10
|
export const entities = [Figure, FigureHistory, FigureReplacedDraft, FigureProposalRecord]
|
|
10
|
-
export const resolvers = [FigureQuery, FigureMutation]
|
|
11
|
+
export const resolvers = [FigureQuery, FigureMutation, FigureNamespaceResolver]
|
|
11
12
|
export const subscribers = [FigureHistoryEntitySubscriber]
|
package/translations/en.json
CHANGED
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"figure.gate.origin-off-centre.how": "Keep it when the figure is fastened there, as a door at its hinge. Move the origin to the middle to place it by its room.",
|
|
101
101
|
"error.figure-namespace-missing": "this domain has no valid figure namespace ({value}). set {setting} first, for example hatiolab.com.",
|
|
102
102
|
"error.figure-namespace-changed": "the figure namespace of this domain is {namespace} and cannot change. set {setting} back to {namespace}.",
|
|
103
|
+
"error.figure-namespace-invalid": "{value} is not a figure namespace. use lower-case letters, digits, dots and hyphens, for example hatiolab.com.",
|
|
103
104
|
"error.figure-type-malformed": "{type} is not a figure type. use lower-case letters, digits and single hyphens, {min} to {max} characters, starting with a letter.",
|
|
104
105
|
"error.figure-name-malformed": "{type} is not a full figure name of the form namespace/type.",
|
|
105
106
|
"error.figure-not-released": "no release is published here.",
|
package/translations/es.json
CHANGED
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"figure.gate.origin-off-centre.how": "Manténgalo si la figura se fija en ese punto, como una puerta por su bisagra. Mueva el origen al centro para colocarla por su espacio.",
|
|
101
101
|
"error.figure-namespace-missing": "este dominio no tiene un namespace de figura válido ({value}). Defina primero {setting}, por ejemplo hatiolab.com.",
|
|
102
102
|
"error.figure-namespace-changed": "el namespace de figura de este dominio es {namespace} y no se puede cambiar. Restablezca {setting} a {namespace}.",
|
|
103
|
+
"error.figure-namespace-invalid": "{value} no es un namespace de Figure. Use minúsculas, dígitos, puntos y guiones, por ejemplo hatiolab.com.",
|
|
103
104
|
"error.figure-type-malformed": "{type} no es un tipo de figura. Use letras minúsculas, dígitos y guiones simples, de {min} a {max} caracteres, empezando por una letra.",
|
|
104
105
|
"error.figure-name-malformed": "{type} no es un nombre completo de figura con el formato namespace/type.",
|
|
105
106
|
"error.figure-not-released": "aquí no hay ninguna publicación.",
|
package/translations/ja.json
CHANGED
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"figure.gate.origin-off-centre.how": "ドアの蝶番のように、Figure をそこで取り付ける場合はそのままにしてください。空間の中央で配置するには、原点を中央に移動してください。",
|
|
101
101
|
"error.figure-namespace-missing": "このドメインには有効な Figure namespace がありません({value})。先に {setting} を設定してください。例: hatiolab.com。",
|
|
102
102
|
"error.figure-namespace-changed": "このドメインの Figure namespace は {namespace} で、変更できません。{setting} を {namespace} に戻してください。",
|
|
103
|
+
"error.figure-namespace-invalid": "Figure namespace の形式ではありません({value})。英小文字・数字・ドット・ハイフンで入力してください。例: hatiolab.com",
|
|
103
104
|
"error.figure-type-malformed": "{type} は Figure type ではありません。英小文字で始め、英小文字・数字・単一のハイフンのみを使って {min}〜{max} 文字で指定してください。",
|
|
104
105
|
"error.figure-name-malformed": "{type} は namespace/type 形式の正式な Figure 名ではありません。",
|
|
105
106
|
"error.figure-not-released": "ここではリリースが公開されていません。",
|
package/translations/ko.json
CHANGED
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"figure.gate.origin-off-centre.how": "문의 경첩처럼 붙이는 자리라면 그대로 둡니다. 공간 가운데로 놓이게 하려면 원점을 가운데로 옮깁니다.",
|
|
101
101
|
"error.figure-namespace-missing": "이 도메인에 Figure namespace 가 없거나 형식이 맞지 않습니다({value}). 설정 {setting} 에 예를 들어 hatiolab.com 을 먼저 넣어 주세요.",
|
|
102
102
|
"error.figure-namespace-changed": "이 도메인의 Figure namespace 는 {namespace} 이며 바꿀 수 없습니다. 설정 {setting} 을 {namespace} 로 되돌려 주세요.",
|
|
103
|
+
"error.figure-namespace-invalid": "Figure namespace 형식이 아닙니다({value}). 영문 소문자·숫자·점·하이픈으로 적어 주세요. 예: hatiolab.com",
|
|
103
104
|
"error.figure-type-malformed": "{type} 은 Figure type 이 아닙니다. 소문자로 시작하고 소문자·숫자·하이픈 하나씩만 써서 {min}~{max}자로 정해 주세요.",
|
|
104
105
|
"error.figure-name-malformed": "{type} 은 namespace/type 형식의 정식 이름이 아닙니다.",
|
|
105
106
|
"error.figure-not-released": "이 도메인에 발행판이 없습니다.",
|
package/translations/ms.json
CHANGED
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"figure.gate.origin-off-centre.how": "Kekalkan jika figure dilekatkan di situ, seperti pintu pada engselnya. Alihkan titik asal ke tengah untuk meletakkannya mengikut ruangnya.",
|
|
101
101
|
"error.figure-namespace-missing": "domain ini tiada namespace figure yang sah ({value}). tetapkan {setting} dahulu, contohnya hatiolab.com.",
|
|
102
102
|
"error.figure-namespace-changed": "namespace figure domain ini ialah {namespace} dan tidak boleh diubah. tetapkan {setting} kembali kepada {namespace}.",
|
|
103
|
+
"error.figure-namespace-invalid": "{value} bukan namespace Figure. Gunakan huruf kecil, digit, titik dan sempang, contohnya hatiolab.com.",
|
|
103
104
|
"error.figure-type-malformed": "{type} bukan jenis figure. gunakan huruf kecil, digit dan tanda sempang tunggal, {min} hingga {max} aksara, bermula dengan huruf.",
|
|
104
105
|
"error.figure-name-malformed": "{type} bukan nama figure penuh dalam bentuk namespace/type.",
|
|
105
106
|
"error.figure-not-released": "tiada terbitan diterbitkan di sini.",
|
package/translations/zh.json
CHANGED
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"figure.gate.origin-off-centre.how": "如果图形固定在该处(如门的铰链),请保持不变。若要按空间放置,请将原点移到中心。",
|
|
101
101
|
"error.figure-namespace-missing": "此域没有有效的图形 namespace({value})。请先设置 {setting},例如 hatiolab.com。",
|
|
102
102
|
"error.figure-namespace-changed": "此域的图形 namespace 为 {namespace},不能更改。请将 {setting} 改回 {namespace}。",
|
|
103
|
+
"error.figure-namespace-invalid": "不是 Figure namespace 的格式({value})。请使用小写字母、数字、点和连字符,例如 hatiolab.com。",
|
|
103
104
|
"error.figure-type-malformed": "{type} 不是图形类型。请使用小写字母、数字和单个连字符,长度 {min} 到 {max} 个字符,并以字母开头。",
|
|
104
105
|
"error.figure-name-malformed": "{type} 不是 namespace/type 形式的完整图形名称。",
|
|
105
106
|
"error.figure-not-released": "此处没有已发布的版本。",
|