@common-grants/core 0.1.1 → 0.2.0
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/README.md +10 -1
- package/lib/api.tsp +93 -1
- package/lib/core/fields/custom-field.tsp +7 -0
- package/lib/core/fields/file.tsp +49 -0
- package/lib/core/fields/index.tsp +1 -0
- package/lib/core/models/applicant-type.tsp +96 -0
- package/lib/core/models/application.tsp +60 -82
- package/lib/core/models/competition.tsp +156 -0
- package/lib/core/models/form-response.tsp +99 -0
- package/lib/core/models/form.tsp +119 -0
- package/lib/core/models/index.tsp +6 -0
- package/lib/core/models/mapping.tsp +152 -0
- package/lib/core/models/opportunity/base.tsp +13 -0
- package/lib/core/models/opportunity/status.tsp +8 -3
- package/lib/core/models/proposal.tsp +173 -0
- package/lib/core/responses/error.tsp +11 -0
- package/lib/core/responses/success.tsp +16 -0
- package/lib/core/routes/applications.tsp +66 -0
- package/lib/core/routes/competitions.tsp +50 -0
- package/lib/core/routes/form-responses.tsp +52 -0
- package/lib/core/routes/forms.tsp +38 -0
- package/lib/core/routes/index.tsp +4 -0
- package/lib/core/routes/opportunities.tsp +14 -6
- package/lib/main.tsp +12 -0
- package/package.json +3 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
namespace CommonGrants.Models;
|
|
2
|
+
|
|
3
|
+
/** The base model for a form response */
|
|
4
|
+
@example(Examples.FormResponse.formResponse)
|
|
5
|
+
model FormResponseBase {
|
|
6
|
+
/** The unique identifier for the form response */
|
|
7
|
+
id: Types.uuid;
|
|
8
|
+
|
|
9
|
+
/** The form being responded to */
|
|
10
|
+
formId: Types.uuid;
|
|
11
|
+
|
|
12
|
+
/** The response to the form */
|
|
13
|
+
response: Record<unknown>;
|
|
14
|
+
|
|
15
|
+
/** The status of the form response */
|
|
16
|
+
status: FormResponseStatus;
|
|
17
|
+
|
|
18
|
+
/** The validation errors for the form response */
|
|
19
|
+
validationErrors?: Array<unknown>;
|
|
20
|
+
|
|
21
|
+
/** Custom attributes about the form response */
|
|
22
|
+
customFields?: Record<Fields.CustomField>;
|
|
23
|
+
|
|
24
|
+
/** The system metadata for the form response */
|
|
25
|
+
...Fields.SystemMetadata;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// #########################################################
|
|
29
|
+
// FormResponseStatus
|
|
30
|
+
// #########################################################
|
|
31
|
+
|
|
32
|
+
/** The status of the form response */
|
|
33
|
+
@example(Examples.FormResponse.inProgressStatus)
|
|
34
|
+
model FormResponseStatus {
|
|
35
|
+
/** The status of the form response, from a predefined set of options */
|
|
36
|
+
value: FormResponseStatusOptions;
|
|
37
|
+
|
|
38
|
+
/** A custom value for the status */
|
|
39
|
+
customValue?: string;
|
|
40
|
+
|
|
41
|
+
/** A human-readable description of the status */
|
|
42
|
+
description?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// #########################################################
|
|
46
|
+
// FormResponseStatusOptions
|
|
47
|
+
// #########################################################
|
|
48
|
+
|
|
49
|
+
/** The set of values accepted for form response status:
|
|
50
|
+
* - `notStarted`: The form response has not been started
|
|
51
|
+
* - `inProgress`: The form response is in progress
|
|
52
|
+
* - `complete`: The form response is complete, meaning all required fields have been filled out and there are no validation errors
|
|
53
|
+
* - `custom`: A custom status
|
|
54
|
+
*/
|
|
55
|
+
enum FormResponseStatusOptions {
|
|
56
|
+
notStarted,
|
|
57
|
+
inProgress,
|
|
58
|
+
complete,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// #########################################################
|
|
62
|
+
// Examples
|
|
63
|
+
// #########################################################
|
|
64
|
+
|
|
65
|
+
namespace Examples.FormResponse {
|
|
66
|
+
const inProgressStatus = #{
|
|
67
|
+
value: FormResponseStatusOptions.inProgress,
|
|
68
|
+
description: "The form response is in progress",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const completeStatus = #{
|
|
72
|
+
value: FormResponseStatusOptions.complete,
|
|
73
|
+
description: "The form response is complete",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const formResponse = #{
|
|
77
|
+
id: "123e4567-e89b-12d3-a456-426614174000",
|
|
78
|
+
formId: "123e4567-e89b-12d3-a456-426614174000",
|
|
79
|
+
response: #{
|
|
80
|
+
firstName: "John",
|
|
81
|
+
lastName: "Doe",
|
|
82
|
+
email: "john.doe@example.com",
|
|
83
|
+
phone: "123-456-7890",
|
|
84
|
+
address: #{
|
|
85
|
+
street: "123 Main St",
|
|
86
|
+
city: "Anytown",
|
|
87
|
+
state: "CA",
|
|
88
|
+
zip: "12345",
|
|
89
|
+
country: null,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
status: inProgressStatus,
|
|
93
|
+
validationErrors: #[
|
|
94
|
+
#{ field: "address.country", message: "Country is required" }
|
|
95
|
+
],
|
|
96
|
+
createdAt: utcDateTime.fromISO("2021-01-01T00:00:00Z"),
|
|
97
|
+
lastModifiedAt: utcDateTime.fromISO("2021-01-01T00:00:00Z"),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import "../index.tsp";
|
|
2
|
+
|
|
3
|
+
namespace CommonGrants.Models;
|
|
4
|
+
|
|
5
|
+
// #########################################################
|
|
6
|
+
// Form
|
|
7
|
+
// #########################################################
|
|
8
|
+
|
|
9
|
+
/** A form for collecting data from a user. */
|
|
10
|
+
@example(Examples.Form.form)
|
|
11
|
+
model Form {
|
|
12
|
+
/** The form's unique identifier. */
|
|
13
|
+
id: Types.uuid;
|
|
14
|
+
|
|
15
|
+
/** The form's name. */
|
|
16
|
+
name: string;
|
|
17
|
+
|
|
18
|
+
/** The form's description. */
|
|
19
|
+
description?: string;
|
|
20
|
+
|
|
21
|
+
/** The form's instructions. */
|
|
22
|
+
instructions?: string | Fields.File[];
|
|
23
|
+
|
|
24
|
+
/** The form's JSON schema used to render the form and validate responses. */
|
|
25
|
+
jsonSchema?: FormJsonSchema;
|
|
26
|
+
|
|
27
|
+
/** The form's UI schema used to render the form in the browser. */
|
|
28
|
+
uiSchema?: FormUISchema;
|
|
29
|
+
|
|
30
|
+
/** A mapping from form schema to CommonGrants schema. */
|
|
31
|
+
mappingToCommonGrants?: Models.MappingSchema;
|
|
32
|
+
|
|
33
|
+
/** A mapping from CommonGrants schema to form schema. */
|
|
34
|
+
mappingFromCommonGrants?: Models.MappingSchema;
|
|
35
|
+
|
|
36
|
+
/** Custom attributes about the form itself, not custom fields within the form. */
|
|
37
|
+
customFields?: Record<Fields.CustomField>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// #########################################################
|
|
41
|
+
// FormJsonSchema
|
|
42
|
+
// #########################################################
|
|
43
|
+
|
|
44
|
+
/** A JSON schema used to validate form responses. */
|
|
45
|
+
@example(Examples.Form.formSchema)
|
|
46
|
+
model FormJsonSchema {
|
|
47
|
+
...Record<unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// #########################################################
|
|
51
|
+
// FormUISchema
|
|
52
|
+
// #########################################################
|
|
53
|
+
|
|
54
|
+
/** A UI schema used to render the form in the browser. */
|
|
55
|
+
@example(Examples.Form.uiSchema)
|
|
56
|
+
model FormUISchema {
|
|
57
|
+
...Record<unknown>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// #########################################################
|
|
61
|
+
// Examples
|
|
62
|
+
// #########################################################
|
|
63
|
+
|
|
64
|
+
namespace Examples.Form {
|
|
65
|
+
const form = #{
|
|
66
|
+
id: "b7c1e2f4-8a3d-4e2a-9c5b-1f2e3d4c5b6a",
|
|
67
|
+
name: "Form A",
|
|
68
|
+
description: "Form A description",
|
|
69
|
+
instructions: "Form A instructions",
|
|
70
|
+
jsonSchema: formSchema,
|
|
71
|
+
uiSchema: uiSchema,
|
|
72
|
+
mappingToCommonGrants: mappingToCommonGrants,
|
|
73
|
+
mappingFromCommonGrants: mappingFromCommonGrants,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const formSchema = #{
|
|
77
|
+
$id: "formA.json",
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: #{
|
|
80
|
+
name: #{ first: #{ type: "string" }, last: #{ type: "string" } },
|
|
81
|
+
email: #{ type: "string" },
|
|
82
|
+
phone: #{ type: "string" },
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const uiSchema = #{
|
|
87
|
+
type: "VerticalLayout",
|
|
88
|
+
elements: #[
|
|
89
|
+
#{
|
|
90
|
+
type: "Group",
|
|
91
|
+
label: "Name",
|
|
92
|
+
elements: #[
|
|
93
|
+
#{ type: "Control", scope: "#/properties/name/first" },
|
|
94
|
+
#{ type: "Control", scope: "#/properties/name/last" }
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
#{ type: "Control", scope: "#/properties/email" },
|
|
98
|
+
#{ type: "Control", scope: "#/properties/phone" }
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const mappingToCommonGrants = #{
|
|
103
|
+
name: #{
|
|
104
|
+
firstName: #{ field: "name.first" },
|
|
105
|
+
lastName: #{ field: "name.last" },
|
|
106
|
+
},
|
|
107
|
+
emails: #{ primary: #{ field: "email" } },
|
|
108
|
+
phones: #{ primary: #{ field: "phone" } },
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const mappingFromCommonGrants = #{
|
|
112
|
+
name: #{
|
|
113
|
+
first: #{ field: "name.firstName" },
|
|
114
|
+
last: #{ field: "name.lastName" },
|
|
115
|
+
},
|
|
116
|
+
email: #{ field: "emails.primary" },
|
|
117
|
+
phone: #{ field: "phones.primary" },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -5,7 +5,13 @@ import "@typespec/json-schema";
|
|
|
5
5
|
import "./opportunity/index.tsp";
|
|
6
6
|
import "./organization.tsp";
|
|
7
7
|
import "./person.tsp";
|
|
8
|
+
import "./proposal.tsp";
|
|
8
9
|
import "./application.tsp";
|
|
10
|
+
import "./competition.tsp";
|
|
11
|
+
import "./form.tsp";
|
|
12
|
+
import "./form-response.tsp";
|
|
13
|
+
import "./mapping.tsp";
|
|
14
|
+
import "./applicant-type.tsp";
|
|
9
15
|
|
|
10
16
|
using TypeSpec.JsonSchema;
|
|
11
17
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
namespace CommonGrants.Models;
|
|
2
|
+
|
|
3
|
+
// #########################################################
|
|
4
|
+
// Mapping
|
|
5
|
+
// #########################################################
|
|
6
|
+
|
|
7
|
+
/** A mapping format for translating data from one schema to another.
|
|
8
|
+
*
|
|
9
|
+
* Example:
|
|
10
|
+
*
|
|
11
|
+
* The following mapping:
|
|
12
|
+
*
|
|
13
|
+
* ```json
|
|
14
|
+
* {
|
|
15
|
+
* "id": { "const": "123" },
|
|
16
|
+
* "opportunity": {
|
|
17
|
+
* "status": {
|
|
18
|
+
* "switch": {
|
|
19
|
+
* "field": "summary.opportunity_status",
|
|
20
|
+
* "case": { "active": "open", "inactive": "closed" },
|
|
21
|
+
* "default": "custom",
|
|
22
|
+
* },
|
|
23
|
+
* },
|
|
24
|
+
* "amount": { "field": "summary.opportunity_amount" },
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Will translate the following data:
|
|
30
|
+
*
|
|
31
|
+
* ```json
|
|
32
|
+
* {
|
|
33
|
+
* "id": "123",
|
|
34
|
+
* "summary": {
|
|
35
|
+
* "opportunity_status": "active",
|
|
36
|
+
* "opportunity_amount": 100,
|
|
37
|
+
* },
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* To the following data:
|
|
42
|
+
*
|
|
43
|
+
* ```json
|
|
44
|
+
* {
|
|
45
|
+
* "id": "123",
|
|
46
|
+
* "opportunity": { "status": "open", "amount": 100 },
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
@example(Examples.Mapping.flatRenaming)
|
|
51
|
+
@example(Examples.Mapping.nestedRenaming)
|
|
52
|
+
@example(Examples.Mapping.simpleSwitch)
|
|
53
|
+
@example(Examples.Mapping.nestedSwitch)
|
|
54
|
+
@example(Examples.Mapping.withLiteralValues)
|
|
55
|
+
model MappingSchema {
|
|
56
|
+
...Record<MappingFunction | MappingSchema>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// #########################################################
|
|
60
|
+
// MappingFunctions
|
|
61
|
+
// #########################################################
|
|
62
|
+
|
|
63
|
+
/** The set of supported mapping functions. */
|
|
64
|
+
@example(Examples.Mapping.constId)
|
|
65
|
+
@example(Examples.Mapping.amountField)
|
|
66
|
+
@example(Examples.Mapping.statusSwitch)
|
|
67
|
+
union MappingFunction {
|
|
68
|
+
`const`: MappingConstantFunction,
|
|
69
|
+
field: MappingFieldFunction,
|
|
70
|
+
switch: MappingSwitchFunction,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// #########################################################
|
|
74
|
+
// MappingConstantFunction
|
|
75
|
+
// #########################################################
|
|
76
|
+
|
|
77
|
+
/** Returns a constant value. */
|
|
78
|
+
@example(Examples.Mapping.constId)
|
|
79
|
+
model MappingConstantFunction {
|
|
80
|
+
`const`: unknown;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// #########################################################
|
|
84
|
+
// MappingFieldFunction
|
|
85
|
+
// #########################################################
|
|
86
|
+
|
|
87
|
+
/** Returns the value of a field in the source data. */
|
|
88
|
+
@example(Examples.Mapping.amountField)
|
|
89
|
+
model MappingFieldFunction {
|
|
90
|
+
field: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// #########################################################
|
|
94
|
+
// MappingSwitchFunction
|
|
95
|
+
// #########################################################
|
|
96
|
+
|
|
97
|
+
/** Returns a new value based on the value of a field in the source data using a switch statement. */
|
|
98
|
+
@example(Examples.Mapping.statusSwitch)
|
|
99
|
+
model MappingSwitchFunction {
|
|
100
|
+
switch: {
|
|
101
|
+
/** The field in the source data to switch on. */
|
|
102
|
+
field: string;
|
|
103
|
+
|
|
104
|
+
/** An object mapping source field values to desired output values. */
|
|
105
|
+
case: Record<unknown>;
|
|
106
|
+
|
|
107
|
+
/** The default value to output if no case matches the source field value. */
|
|
108
|
+
default?: unknown;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// #########################################################
|
|
113
|
+
// Examples
|
|
114
|
+
// #########################################################
|
|
115
|
+
|
|
116
|
+
namespace Examples.Mapping {
|
|
117
|
+
const constId = #{ `const`: "123" };
|
|
118
|
+
const amountField = #{ field: "summary.opportunity_amount" };
|
|
119
|
+
const statusField = #{ field: "summary.opportunity_status" };
|
|
120
|
+
const statusSwitch = #{
|
|
121
|
+
switch: #{
|
|
122
|
+
field: "summary.opportunity_status",
|
|
123
|
+
case: #{ active: "open", inactive: "closed" },
|
|
124
|
+
default: "custom",
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const flatRenaming = #{
|
|
129
|
+
id: constId,
|
|
130
|
+
opportunityStatus: statusField,
|
|
131
|
+
opportunityAmount: amountField,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const nestedRenaming = #{
|
|
135
|
+
id: constId,
|
|
136
|
+
opportunity: #{ status: statusField, amount: amountField },
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const simpleSwitch = #{
|
|
140
|
+
opportunityAmount: amountField,
|
|
141
|
+
opportunityStatus: statusSwitch,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const nestedSwitch = #{
|
|
145
|
+
opportunity: #{ status: statusSwitch, amount: amountField },
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const withLiteralValues = #{
|
|
149
|
+
id: constId,
|
|
150
|
+
opportunity: #{ status: statusSwitch, amount: amountField },
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -62,6 +62,9 @@ model OpportunityBase {
|
|
|
62
62
|
/** Key dates for the opportunity, such as when the application opens and closes */
|
|
63
63
|
keyDates?: OppTimeline;
|
|
64
64
|
|
|
65
|
+
/** The type of applicant for the opportunity */
|
|
66
|
+
acceptedApplicantTypes?: ApplicantType[];
|
|
67
|
+
|
|
65
68
|
/** URL for the original source of the opportunity */
|
|
66
69
|
source?: url;
|
|
67
70
|
|
|
@@ -71,3 +74,13 @@ model OpportunityBase {
|
|
|
71
74
|
// Spreads the fields from the Metadata model into the Opportunity model
|
|
72
75
|
...SystemMetadata;
|
|
73
76
|
}
|
|
77
|
+
|
|
78
|
+
// ########################################
|
|
79
|
+
// Opportunity details
|
|
80
|
+
// ########################################
|
|
81
|
+
|
|
82
|
+
/** A funding opportunity with additional details, like available competitions. */
|
|
83
|
+
model OpportunityDetails extends OpportunityBase {
|
|
84
|
+
/** The competitions associated with the opportunity */
|
|
85
|
+
competitions?: CompetitionBase[];
|
|
86
|
+
}
|
|
@@ -4,7 +4,12 @@ namespace CommonGrants.Models;
|
|
|
4
4
|
// Opportunity status options
|
|
5
5
|
// ########################################
|
|
6
6
|
|
|
7
|
-
/** The set of values accepted for opportunity status
|
|
7
|
+
/** The set of values accepted for opportunity status:
|
|
8
|
+
* - `forecasted`: The opportunity is forecasted and not yet open for applications
|
|
9
|
+
* - `open`: The opportunity is open for applications
|
|
10
|
+
* - `closed`: The opportunity is no longer accepting applications
|
|
11
|
+
* - `custom`: A custom status
|
|
12
|
+
*/
|
|
8
13
|
enum OppStatusOptions {
|
|
9
14
|
forecasted,
|
|
10
15
|
open,
|
|
@@ -20,10 +25,10 @@ enum OppStatusOptions {
|
|
|
20
25
|
@example(Examples.OppStatus.custom)
|
|
21
26
|
@example(Examples.OppStatus.default)
|
|
22
27
|
model OppStatus {
|
|
23
|
-
/** The status
|
|
28
|
+
/** The status of the opportunity, from a predefined set of options */
|
|
24
29
|
value: OppStatusOptions;
|
|
25
30
|
|
|
26
|
-
/** A custom status
|
|
31
|
+
/** A custom value for the status */
|
|
27
32
|
customValue?: string;
|
|
28
33
|
|
|
29
34
|
/** A human-readable description of the status */
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
namespace CommonGrants.Models;
|
|
2
|
+
|
|
3
|
+
// #########################################################
|
|
4
|
+
// ProposalBase
|
|
5
|
+
// #########################################################
|
|
6
|
+
|
|
7
|
+
/** A proposal for funding. */
|
|
8
|
+
@example(Examples.Proposal.exampleProposal)
|
|
9
|
+
model ProposalBase {
|
|
10
|
+
/** The title of the proposal and/or the project requesting funding. */
|
|
11
|
+
title?: string;
|
|
12
|
+
|
|
13
|
+
/** The description of the proposal and/or the project requesting funding. */
|
|
14
|
+
description?: string;
|
|
15
|
+
|
|
16
|
+
/** The amount of money requested. */
|
|
17
|
+
amountRequested?: Fields.Money;
|
|
18
|
+
|
|
19
|
+
/** The key dates for the project. */
|
|
20
|
+
projectTimeline?: ProjectTimeline;
|
|
21
|
+
|
|
22
|
+
/** The opportunity to which this proposal is related */
|
|
23
|
+
opportunity?: ProposalOpportunity;
|
|
24
|
+
|
|
25
|
+
/** The organization that is requesting funding. */
|
|
26
|
+
organizations?: ProposalOrgs;
|
|
27
|
+
|
|
28
|
+
/** The point of contact for the project. */
|
|
29
|
+
contacts?: ProposalContacts;
|
|
30
|
+
|
|
31
|
+
/** The project's custom fields. */
|
|
32
|
+
customFields?: Record<Fields.CustomField>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// #########################################################
|
|
36
|
+
// ProposalOpportunity
|
|
37
|
+
// #########################################################
|
|
38
|
+
|
|
39
|
+
/** The opportunity to which this proposal is related */
|
|
40
|
+
model ProposalOpportunity {
|
|
41
|
+
/** The opportunity's unique identifier. */
|
|
42
|
+
id: Types.uuid;
|
|
43
|
+
|
|
44
|
+
/** The opportunity's name. */
|
|
45
|
+
title?: string;
|
|
46
|
+
|
|
47
|
+
/** The opportunity's custom fields. */
|
|
48
|
+
customFields?: Record<Fields.CustomField>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// #########################################################
|
|
52
|
+
// ProjectTimeline
|
|
53
|
+
// #########################################################
|
|
54
|
+
|
|
55
|
+
model ProjectTimeline {
|
|
56
|
+
/** The start date of the period for which the funding is requested. */
|
|
57
|
+
startDate?: Fields.Event;
|
|
58
|
+
|
|
59
|
+
/** The end date of the period for which the funding is requested. */
|
|
60
|
+
endDate?: Fields.Event;
|
|
61
|
+
|
|
62
|
+
/** The key dates for the project. */
|
|
63
|
+
otherDates?: Record<Fields.Event>;
|
|
64
|
+
|
|
65
|
+
/** Details about the timeline that don't fit into the other fields. */
|
|
66
|
+
timelineDetails?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// #########################################################
|
|
70
|
+
// ProjectContacts
|
|
71
|
+
// #########################################################
|
|
72
|
+
|
|
73
|
+
model ProposalContacts {
|
|
74
|
+
/** The primary point of contact for the proposal. */
|
|
75
|
+
primary: PersonBase;
|
|
76
|
+
|
|
77
|
+
/** Other points of contact for the proposal. For example, key personnel, authorized representatives, etc. */
|
|
78
|
+
otherContacts?: Record<PersonBase>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// #########################################################
|
|
82
|
+
// ProposalOrgs
|
|
83
|
+
// #########################################################
|
|
84
|
+
|
|
85
|
+
model ProposalOrgs {
|
|
86
|
+
/** The primary organization that is requesting funding. */
|
|
87
|
+
primary: OrganizationBase;
|
|
88
|
+
|
|
89
|
+
/** Other organizations that are supporting the proposal. For example, a fiscal sponsor, partners, etc. */
|
|
90
|
+
otherOrgs?: Record<OrganizationBase>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// #########################################################
|
|
94
|
+
// Examples
|
|
95
|
+
// #########################################################
|
|
96
|
+
|
|
97
|
+
namespace Examples.Proposal {
|
|
98
|
+
const exampleProposal = #{
|
|
99
|
+
title: "Example Project",
|
|
100
|
+
description: "Example project to serve community needs.",
|
|
101
|
+
amountRequested: #{ amount: "100000", currency: "USD" },
|
|
102
|
+
opportunity: exampleOpportunity,
|
|
103
|
+
projectTimeline: timeline,
|
|
104
|
+
contacts: contacts,
|
|
105
|
+
organizations: organizations,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// #####################################
|
|
109
|
+
// Opportunity
|
|
110
|
+
// #####################################
|
|
111
|
+
|
|
112
|
+
const exampleOpportunity = #{
|
|
113
|
+
id: "083b4567-e89d-42c8-a439-6c1234567890",
|
|
114
|
+
title: "Example Opportunity",
|
|
115
|
+
customFields: #{ agency: Fields.Examples.CustomField.agency },
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// #####################################
|
|
119
|
+
// ProjectTimeline
|
|
120
|
+
// #####################################
|
|
121
|
+
|
|
122
|
+
const timeline = #{
|
|
123
|
+
startDate: #{
|
|
124
|
+
name: "Project Start Date",
|
|
125
|
+
eventType: Fields.EventType.singleDate,
|
|
126
|
+
date: Types.isoDate.fromISO("2025-01-01"),
|
|
127
|
+
},
|
|
128
|
+
endDate: #{
|
|
129
|
+
name: "Project End Date",
|
|
130
|
+
eventType: Fields.EventType.singleDate,
|
|
131
|
+
date: Types.isoDate.fromISO("2025-12-31"),
|
|
132
|
+
},
|
|
133
|
+
otherDates: #{
|
|
134
|
+
evaluationPeriod: #{
|
|
135
|
+
name: "Evaluation Period",
|
|
136
|
+
eventType: Fields.EventType.dateRange,
|
|
137
|
+
startDate: Types.isoDate.fromISO("2025-07-01"),
|
|
138
|
+
endDate: Types.isoDate.fromISO("2025-08-31"),
|
|
139
|
+
description: "The period during which the evaluation will be conducted.",
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// #####################################
|
|
145
|
+
// Contacts
|
|
146
|
+
// #####################################
|
|
147
|
+
|
|
148
|
+
const contacts = #{
|
|
149
|
+
primary: Examples.Person.examplePerson,
|
|
150
|
+
otherContacts: #{
|
|
151
|
+
principalInvestigator: #{
|
|
152
|
+
name: #{ prefix: "Dr.", firstName: "Alicia", lastName: "Williams" },
|
|
153
|
+
emails: #{ primary: "alicia.williams@example.com" },
|
|
154
|
+
},
|
|
155
|
+
authorizedRepresentative: #{
|
|
156
|
+
name: #{ firstName: "John", lastName: "Doe" },
|
|
157
|
+
emails: #{ primary: "john.doe@example.com" },
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// #####################################
|
|
163
|
+
// Organizations
|
|
164
|
+
// #####################################
|
|
165
|
+
|
|
166
|
+
const organizations = #{
|
|
167
|
+
primary: Examples.Organization.exampleOrg,
|
|
168
|
+
otherOrgs: #{
|
|
169
|
+
fiscalSponsor: Examples.Organization.exampleOrg,
|
|
170
|
+
partner: Examples.Organization.exampleOrg,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -28,3 +28,14 @@ model Error {
|
|
|
28
28
|
|
|
29
29
|
alias Unauthorized = Error & Http.UnauthorizedResponse;
|
|
30
30
|
alias NotFound = Error & Http.NotFoundResponse;
|
|
31
|
+
|
|
32
|
+
@example(#{
|
|
33
|
+
status: 400,
|
|
34
|
+
message: "Application submission failed due to validation errors",
|
|
35
|
+
errors: #[#{ field: "formA.name", message: "Name is required" }],
|
|
36
|
+
})
|
|
37
|
+
@doc("A failure to submit an application due to validation errors")
|
|
38
|
+
model ApplicationSubmissionError extends Error {
|
|
39
|
+
@example(400)
|
|
40
|
+
status: 400;
|
|
41
|
+
}
|
|
@@ -144,3 +144,19 @@ model Filtered<ItemsT, FilterT> extends Success {
|
|
|
144
144
|
errors?: string[];
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
|
+
|
|
148
|
+
// ############################################################################
|
|
149
|
+
// 201 response
|
|
150
|
+
// ############################################################################
|
|
151
|
+
|
|
152
|
+
/** A 201 response with data
|
|
153
|
+
*
|
|
154
|
+
* @template T The schema for the value of the `"data"` property in this response
|
|
155
|
+
*/
|
|
156
|
+
model Created<T> extends Success {
|
|
157
|
+
// Inherit the 201 status code
|
|
158
|
+
...Http.CreatedResponse;
|
|
159
|
+
|
|
160
|
+
/** Response data */
|
|
161
|
+
data: T;
|
|
162
|
+
}
|