@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
package/README.md
CHANGED
|
@@ -34,12 +34,14 @@ The Opportunity model is templated to support custom fields. First define your c
|
|
|
34
34
|
// models.tsp
|
|
35
35
|
|
|
36
36
|
import "@common-grants/core"; // Import the base specification library
|
|
37
|
+
import "@typespec/versioning";
|
|
37
38
|
|
|
38
39
|
// Allows us to use models and fields defined in the core library without
|
|
39
40
|
// prefixing each item with `CommonGrants.Models` or `CommonGrants.Fields`
|
|
40
41
|
using CommonGrants.Models;
|
|
41
42
|
using CommonGrants.Fields;
|
|
42
43
|
|
|
44
|
+
@Versioning.useDependency(CommonGrants.Versions.v0_2) // Specify the version of the core library to use
|
|
43
45
|
namespace CustomAPI.CustomModels;
|
|
44
46
|
|
|
45
47
|
// Define a custom field
|
|
@@ -77,6 +79,7 @@ using TypeSpec.Http;
|
|
|
77
79
|
|
|
78
80
|
@tag("Search")
|
|
79
81
|
@route("/common-grants/opportunities")
|
|
82
|
+
@Versioning.useDependency(CommonGrants.Versions.v0_2)
|
|
80
83
|
namespace CustomAPI.CustomRoutes {
|
|
81
84
|
alias OpportunitiesRouter = Opportunities;
|
|
82
85
|
|
|
@@ -117,10 +120,16 @@ Or specify the emitter in `tspconfig.yaml`:
|
|
|
117
120
|
|
|
118
121
|
```yaml
|
|
119
122
|
# tspconfig.yaml
|
|
120
|
-
|
|
123
|
+
emit:
|
|
121
124
|
- "@typespec/openapi3"
|
|
122
125
|
```
|
|
123
126
|
|
|
127
|
+
And run the following command:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx tsp compile main.tsp
|
|
131
|
+
```
|
|
132
|
+
|
|
124
133
|
Both strategies will generate an OpenAPI specification in the `tsp-output/` directory.
|
|
125
134
|
|
|
126
135
|
### Further reading
|
package/lib/api.tsp
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import "./core/index.tsp";
|
|
3
3
|
import "@typespec/http";
|
|
4
4
|
import "@typespec/openapi";
|
|
5
|
+
import "@typespec/versioning";
|
|
5
6
|
|
|
6
7
|
using TypeSpec.Http;
|
|
7
8
|
using TypeSpec.OpenAPI;
|
|
9
|
+
using Versioning;
|
|
8
10
|
|
|
9
11
|
/** The base OpenAPI specification for a CommonGrants API
|
|
10
12
|
*
|
|
@@ -12,6 +14,12 @@ using TypeSpec.OpenAPI;
|
|
|
12
14
|
* it must implement all of the routes with the "required" tag in this specification.
|
|
13
15
|
*/
|
|
14
16
|
@service(#{ title: "CommonGrants Base API" })
|
|
17
|
+
@tagMetadata(
|
|
18
|
+
"experimental",
|
|
19
|
+
#{
|
|
20
|
+
description: "Endpoints that MAY be implemented by CommonGrants APIs, but are not guaranteed to be stable",
|
|
21
|
+
}
|
|
22
|
+
)
|
|
15
23
|
@tagMetadata(
|
|
16
24
|
"optional",
|
|
17
25
|
#{ description: "Endpoints that MAY be implemented by CommonGrants APIs" }
|
|
@@ -22,14 +30,30 @@ using TypeSpec.OpenAPI;
|
|
|
22
30
|
description: "Endpoints that MUST be implemented by all CommonGrants APIs",
|
|
23
31
|
}
|
|
24
32
|
)
|
|
33
|
+
@tagMetadata("Forms", #{ description: "Endpoints related to forms" })
|
|
34
|
+
@tagMetadata(
|
|
35
|
+
"Applications",
|
|
36
|
+
#{ description: "Endpoints related to applications for a given competition" }
|
|
37
|
+
)
|
|
38
|
+
@tagMetadata(
|
|
39
|
+
"Competitions",
|
|
40
|
+
#{
|
|
41
|
+
description: "Endpoints related to competitions, which are distinct application processes for the same funding opportunity",
|
|
42
|
+
}
|
|
43
|
+
)
|
|
25
44
|
@tagMetadata(
|
|
26
45
|
"Opportunities",
|
|
27
46
|
#{ description: "Endpoints related to funding opportunities" }
|
|
28
47
|
)
|
|
48
|
+
@route("/common-grants") // Prefixes all routes with `/common-grants/`
|
|
29
49
|
namespace CommonGrants.API;
|
|
30
50
|
|
|
51
|
+
// #########################################################
|
|
52
|
+
// Opportunities
|
|
53
|
+
// #########################################################
|
|
54
|
+
|
|
31
55
|
@tag("Opportunities")
|
|
32
|
-
@route("/
|
|
56
|
+
@route("/opportunities")
|
|
33
57
|
namespace Opportunities {
|
|
34
58
|
alias Router = Routes.Opportunities;
|
|
35
59
|
|
|
@@ -42,3 +66,71 @@ namespace Opportunities {
|
|
|
42
66
|
@tag("optional")
|
|
43
67
|
op search is Router.search;
|
|
44
68
|
}
|
|
69
|
+
|
|
70
|
+
// #########################################################
|
|
71
|
+
// Competitions
|
|
72
|
+
// #########################################################
|
|
73
|
+
|
|
74
|
+
@tag("Competitions")
|
|
75
|
+
@tag("experimental")
|
|
76
|
+
@route("/competitions")
|
|
77
|
+
namespace Competitions {
|
|
78
|
+
alias Router = Routes.Competitions;
|
|
79
|
+
|
|
80
|
+
op read is Router.read;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// #########################################################
|
|
84
|
+
// Applications
|
|
85
|
+
// #########################################################
|
|
86
|
+
|
|
87
|
+
@tag("Applications")
|
|
88
|
+
@tag("experimental")
|
|
89
|
+
@route("/applications")
|
|
90
|
+
namespace Applications {
|
|
91
|
+
alias ApplicationRouter = Routes.Applications;
|
|
92
|
+
alias FormResponseRouter = Routes.FormResponses;
|
|
93
|
+
|
|
94
|
+
// ################################
|
|
95
|
+
// Start an application workflow
|
|
96
|
+
// ################################
|
|
97
|
+
|
|
98
|
+
@added(Versions.v0_2)
|
|
99
|
+
op startApplication is ApplicationRouter.startApplication;
|
|
100
|
+
|
|
101
|
+
@added(Versions.v0_2)
|
|
102
|
+
op getApplication is ApplicationRouter.getApplication;
|
|
103
|
+
|
|
104
|
+
// ################################
|
|
105
|
+
// Update form responses
|
|
106
|
+
// ################################
|
|
107
|
+
|
|
108
|
+
@added(Versions.v0_2)
|
|
109
|
+
op setFormResponse is FormResponseRouter.setFormResponse;
|
|
110
|
+
|
|
111
|
+
@added(Versions.v0_2)
|
|
112
|
+
op getFormResponse is FormResponseRouter.getFormResponse;
|
|
113
|
+
|
|
114
|
+
// ################################
|
|
115
|
+
// Submit an application after completing all forms
|
|
116
|
+
// ################################
|
|
117
|
+
|
|
118
|
+
@added(Versions.v0_2)
|
|
119
|
+
op submitApplication is ApplicationRouter.submitApplication;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// #########################################################
|
|
123
|
+
// Forms
|
|
124
|
+
// #########################################################
|
|
125
|
+
|
|
126
|
+
@tag("Forms")
|
|
127
|
+
@route("/forms")
|
|
128
|
+
namespace Forms {
|
|
129
|
+
alias Router = Routes.Forms;
|
|
130
|
+
|
|
131
|
+
@tag("optional")
|
|
132
|
+
op list is Router.list;
|
|
133
|
+
|
|
134
|
+
@tag("optional")
|
|
135
|
+
op read is Router.read;
|
|
136
|
+
}
|
|
@@ -76,6 +76,13 @@ namespace Examples.CustomField {
|
|
|
76
76
|
schema: "https://example.com/program-areas.json",
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
const agency = #{
|
|
80
|
+
name: "agency",
|
|
81
|
+
fieldType: CustomFieldType.string,
|
|
82
|
+
value: "Department of Transportation",
|
|
83
|
+
description: "The agency responsible for managing this opportunity",
|
|
84
|
+
};
|
|
85
|
+
|
|
79
86
|
/** An example of an array custom field */
|
|
80
87
|
const eligibilityTypes = #{
|
|
81
88
|
name: "eligibilityType",
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
|
+
|
|
3
|
+
/** A field representing a downloadable file. */
|
|
4
|
+
@example(Examples.File.imageFile, #{ title: "An image file" })
|
|
5
|
+
@example(Examples.File.pdfFile, #{ title: "A PDF file" })
|
|
6
|
+
model File {
|
|
7
|
+
/** The file's download URL. */
|
|
8
|
+
downloadUrl: url;
|
|
9
|
+
|
|
10
|
+
/** The file's name. */
|
|
11
|
+
name: string;
|
|
12
|
+
|
|
13
|
+
/** The file's description. */
|
|
14
|
+
description?: string;
|
|
15
|
+
|
|
16
|
+
/** The file's size in bytes. */
|
|
17
|
+
sizeInBytes?: numeric;
|
|
18
|
+
|
|
19
|
+
/** The file's MIME type. */
|
|
20
|
+
mimeType?: string;
|
|
21
|
+
|
|
22
|
+
/** The system metadata for the file. */
|
|
23
|
+
...Fields.SystemMetadata;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// #########################################################
|
|
27
|
+
// Examples
|
|
28
|
+
// #########################################################
|
|
29
|
+
|
|
30
|
+
namespace Examples.File {
|
|
31
|
+
const pdfFile = #{
|
|
32
|
+
downloadUrl: "https://example.com/file.pdf",
|
|
33
|
+
name: "example.pdf",
|
|
34
|
+
description: "A PDF file with instructions",
|
|
35
|
+
sizeInBytes: 1000,
|
|
36
|
+
mimeType: "application/pdf",
|
|
37
|
+
createdAt: utcDateTime.fromISO("2025-01-01T17:01:01"),
|
|
38
|
+
lastModifiedAt: utcDateTime.fromISO("2025-01-02T17:30:00"),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const imageFile = #{
|
|
42
|
+
downloadUrl: "https://example.com/image.png",
|
|
43
|
+
name: "image.png",
|
|
44
|
+
sizeInBytes: 1000,
|
|
45
|
+
mimeType: "image/png",
|
|
46
|
+
createdAt: utcDateTime.fromISO("2025-01-01T17:01:01"),
|
|
47
|
+
lastModifiedAt: utcDateTime.fromISO("2025-01-02T17:30:00"),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
namespace CommonGrants.Models;
|
|
2
|
+
|
|
3
|
+
// #########################################################
|
|
4
|
+
// ApplicantType
|
|
5
|
+
// #########################################################
|
|
6
|
+
|
|
7
|
+
/** The type of applicant eligible to apply for funding */
|
|
8
|
+
@example(Examples.ApplicantType.organization)
|
|
9
|
+
@example(Examples.ApplicantType.individual)
|
|
10
|
+
model ApplicantType {
|
|
11
|
+
/** The type of applicant */
|
|
12
|
+
value: ApplicantTypeOptions;
|
|
13
|
+
|
|
14
|
+
/** The custom value for the applicant type */
|
|
15
|
+
customValue?: string;
|
|
16
|
+
|
|
17
|
+
/** The description of the applicant type */
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// #########################################################
|
|
22
|
+
// ApplicantTypeOptions
|
|
23
|
+
// #########################################################
|
|
24
|
+
|
|
25
|
+
/** The set of possible applicant types */
|
|
26
|
+
enum ApplicantTypeOptions {
|
|
27
|
+
/** The applicant is an individual */
|
|
28
|
+
individual,
|
|
29
|
+
|
|
30
|
+
/** Any type of organization */
|
|
31
|
+
organization,
|
|
32
|
+
|
|
33
|
+
/** State government */
|
|
34
|
+
government_state,
|
|
35
|
+
|
|
36
|
+
/** County government */
|
|
37
|
+
government_county,
|
|
38
|
+
|
|
39
|
+
/** City or township government */
|
|
40
|
+
government_municipal,
|
|
41
|
+
|
|
42
|
+
/** Special district government */
|
|
43
|
+
government_special_district,
|
|
44
|
+
|
|
45
|
+
/** Federally recognized Native American tribal government */
|
|
46
|
+
government_tribal,
|
|
47
|
+
|
|
48
|
+
/** Native American tribal organization that is not federally recognized */
|
|
49
|
+
organization_tribal_other,
|
|
50
|
+
|
|
51
|
+
/** Independent school district */
|
|
52
|
+
school_district_independent,
|
|
53
|
+
|
|
54
|
+
/** Public or state institution of higher education */
|
|
55
|
+
higher_education_public,
|
|
56
|
+
|
|
57
|
+
/** Private institution of higher education */
|
|
58
|
+
higher_education_private,
|
|
59
|
+
|
|
60
|
+
/** Non-profit organization with 501(c)(3) status */
|
|
61
|
+
non_profit_with_501c3,
|
|
62
|
+
|
|
63
|
+
/** Non-profit organization without 501(c)(3) status */
|
|
64
|
+
nonprofit_without_501c3,
|
|
65
|
+
|
|
66
|
+
/** For-profit small business */
|
|
67
|
+
for_profit_small_business,
|
|
68
|
+
|
|
69
|
+
/** For-profit organization that is not a small business */
|
|
70
|
+
for_profit_not_small_business,
|
|
71
|
+
|
|
72
|
+
/** Anyone can apply (unrestricted) */
|
|
73
|
+
unrestricted,
|
|
74
|
+
|
|
75
|
+
/** Custom applicant type */
|
|
76
|
+
custom,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// #########################################################
|
|
80
|
+
// ApplicantTypeExamples
|
|
81
|
+
// #########################################################
|
|
82
|
+
|
|
83
|
+
/** Examples of applicant types */
|
|
84
|
+
namespace Examples.ApplicantType {
|
|
85
|
+
/** The applicant is an individual */
|
|
86
|
+
const individual = #{
|
|
87
|
+
value: ApplicantTypeOptions.individual,
|
|
88
|
+
description: "An individual applicant",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Any type of organization */
|
|
92
|
+
const organization = #{
|
|
93
|
+
value: ApplicantTypeOptions.organization,
|
|
94
|
+
description: "Any type of organization",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -1,49 +1,50 @@
|
|
|
1
|
-
import "../index.tsp";
|
|
2
|
-
|
|
3
1
|
namespace CommonGrants.Models;
|
|
4
2
|
|
|
5
|
-
/** The base model for an application
|
|
6
|
-
@example(Examples.Application.
|
|
3
|
+
/** The base model for an application to a competition for a funding opportunity */
|
|
4
|
+
@example(Examples.Application.applicationBase)
|
|
7
5
|
model ApplicationBase {
|
|
8
|
-
/** The
|
|
6
|
+
/** The unique identifier for the application */
|
|
9
7
|
id: Types.uuid;
|
|
10
8
|
|
|
11
|
-
/** The application
|
|
12
|
-
|
|
9
|
+
/** The name of the application */
|
|
10
|
+
name: string;
|
|
13
11
|
|
|
14
|
-
/** The
|
|
15
|
-
|
|
12
|
+
/** The unique identifier for the competition */
|
|
13
|
+
competitionId: Types.uuid;
|
|
16
14
|
|
|
17
|
-
/** The
|
|
18
|
-
|
|
15
|
+
/** The form responses for the application */
|
|
16
|
+
formResponses: Record<AppFormResponse>;
|
|
19
17
|
|
|
20
|
-
/** The
|
|
21
|
-
|
|
18
|
+
/** The status of the application */
|
|
19
|
+
status: AppStatus;
|
|
22
20
|
|
|
23
|
-
/** The application
|
|
24
|
-
|
|
21
|
+
/** The date and time the application was submitted */
|
|
22
|
+
submittedAt?: utcDateTime | null;
|
|
25
23
|
|
|
26
|
-
/** The
|
|
27
|
-
|
|
24
|
+
/** The validation errors for the application and form responses */
|
|
25
|
+
validationErrors?: Array<unknown>;
|
|
28
26
|
|
|
29
|
-
/** The
|
|
27
|
+
/** The custom fields about the application */
|
|
30
28
|
customFields?: Record<Fields.CustomField>;
|
|
29
|
+
|
|
30
|
+
/** The system metadata for the application */
|
|
31
|
+
...Fields.SystemMetadata;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
// #########################################################
|
|
34
35
|
// AppStatus
|
|
35
36
|
// #########################################################
|
|
36
37
|
|
|
37
|
-
/** The status of the application
|
|
38
|
+
/** The status of the application */
|
|
38
39
|
@example(Examples.Application.submittedStatus)
|
|
39
40
|
model AppStatus {
|
|
40
|
-
/** The status of the application
|
|
41
|
+
/** The status of the application, from a predefined set of options */
|
|
41
42
|
value: AppStatusOptions;
|
|
42
43
|
|
|
43
|
-
/** A custom value for the status
|
|
44
|
+
/** A custom value for the status */
|
|
44
45
|
customValue?: string;
|
|
45
46
|
|
|
46
|
-
/** A description of the status
|
|
47
|
+
/** A human-readable description of the status */
|
|
47
48
|
description?: string;
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -51,54 +52,33 @@ model AppStatus {
|
|
|
51
52
|
// AppStatusOptions
|
|
52
53
|
// #########################################################
|
|
53
54
|
|
|
54
|
-
/** The default set of values accepted for application status
|
|
55
|
+
/** The default set of values accepted for application status:
|
|
56
|
+
* - `inProgress`: The application is in progress
|
|
57
|
+
* - `submitted`: The application has been submitted and is being reviewed
|
|
58
|
+
* - `accepted`: The application has been accepted
|
|
59
|
+
* - `rejected`: The application has been rejected
|
|
60
|
+
* - `custom`: A custom status
|
|
61
|
+
*/
|
|
55
62
|
enum AppStatusOptions {
|
|
63
|
+
inProgress,
|
|
56
64
|
submitted,
|
|
57
|
-
|
|
65
|
+
accepted,
|
|
58
66
|
rejected,
|
|
59
67
|
custom,
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
// #########################################################
|
|
63
|
-
//
|
|
64
|
-
// #########################################################
|
|
65
|
-
|
|
66
|
-
/** The project for which funding is requested. */
|
|
67
|
-
@example(Examples.Application.exampleProposal)
|
|
68
|
-
model AppProposal {
|
|
69
|
-
/** The title of the proposal and/or the project requesting funding. */
|
|
70
|
-
title: string;
|
|
71
|
-
|
|
72
|
-
/** The description of the proposal and/or the project requesting funding. */
|
|
73
|
-
description: string;
|
|
74
|
-
|
|
75
|
-
/** The amount of money requested. */
|
|
76
|
-
amountRequested?: Fields.Money;
|
|
77
|
-
|
|
78
|
-
/** The start date of the period for which the funding is requested. */
|
|
79
|
-
periodStartDate?: Types.isoDate;
|
|
80
|
-
|
|
81
|
-
/** The end date of the period for which the funding is requested. */
|
|
82
|
-
periodEndDate?: Types.isoDate;
|
|
83
|
-
|
|
84
|
-
/** The project's custom fields. */
|
|
85
|
-
customFields?: Record<Fields.CustomField>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// #########################################################
|
|
89
|
-
// AppOpportunity
|
|
71
|
+
// ApplicationFormResponse
|
|
90
72
|
// #########################################################
|
|
91
73
|
|
|
92
|
-
/** The
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/** The opportunity's name. */
|
|
98
|
-
title?: string;
|
|
74
|
+
/** The model for a form response included in an application */
|
|
75
|
+
@example(Examples.Application.formResponse)
|
|
76
|
+
model AppFormResponse {
|
|
77
|
+
/** The unique identifier for the application */
|
|
78
|
+
applicationId: Types.uuid;
|
|
99
79
|
|
|
100
|
-
/**
|
|
101
|
-
|
|
80
|
+
/** Includes all the fields from the FormResponseBase model */
|
|
81
|
+
...FormResponseBase;
|
|
102
82
|
}
|
|
103
83
|
|
|
104
84
|
// #########################################################
|
|
@@ -106,37 +86,35 @@ model AppOpportunity {
|
|
|
106
86
|
// #########################################################
|
|
107
87
|
|
|
108
88
|
namespace Examples.Application {
|
|
109
|
-
const exampleApplication = #{
|
|
110
|
-
id: "083b4567-e89d-42c8-a439-6c1234567890",
|
|
111
|
-
status: submittedStatus,
|
|
112
|
-
dateSubmitted: Types.isoDate.fromISO("2024-01-01"),
|
|
113
|
-
organization: Examples.Organization.exampleOrg,
|
|
114
|
-
pointOfContact: Examples.Person.examplePerson,
|
|
115
|
-
proposal: exampleProposal,
|
|
116
|
-
opportunity: exampleOpportunity,
|
|
117
|
-
};
|
|
118
|
-
|
|
119
89
|
const submittedStatus = #{
|
|
120
90
|
value: AppStatusOptions.submitted,
|
|
121
|
-
description: "
|
|
91
|
+
description: "The application has been submitted.",
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const inProgressStatus = #{
|
|
95
|
+
value: AppStatusOptions.inProgress,
|
|
96
|
+
description: "The application is in progress.",
|
|
122
97
|
};
|
|
123
98
|
|
|
124
99
|
const customStatus = #{
|
|
125
100
|
value: AppStatusOptions.custom,
|
|
126
|
-
customValue: "
|
|
127
|
-
description: "Application
|
|
101
|
+
customValue: "cancelled",
|
|
102
|
+
description: "Application was cancelled before it was submitted.",
|
|
128
103
|
};
|
|
129
104
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
amountRequested: #{ amount: "100000", currency: "USD" },
|
|
134
|
-
periodStartDate: Types.isoDate.fromISO("2024-01-01"),
|
|
135
|
-
periodEndDate: Types.isoDate.fromISO("2024-12-31"),
|
|
105
|
+
const formResponse = #{
|
|
106
|
+
applicationId: "123e4567-e89b-12d3-a456-426614174000",
|
|
107
|
+
...Examples.FormResponse.formResponse,
|
|
136
108
|
};
|
|
137
109
|
|
|
138
|
-
const
|
|
139
|
-
id: "
|
|
140
|
-
|
|
110
|
+
const applicationBase = #{
|
|
111
|
+
id: "123e4567-e89b-12d3-a456-426614174000",
|
|
112
|
+
name: "My Application",
|
|
113
|
+
competitionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
114
|
+
formResponses: #{ formA: formResponse },
|
|
115
|
+
status: inProgressStatus,
|
|
116
|
+
submittedAt: null,
|
|
117
|
+
createdAt: utcDateTime.fromISO("2021-01-01T00:00:00Z"),
|
|
118
|
+
lastModifiedAt: utcDateTime.fromISO("2021-01-01T00:00:00Z"),
|
|
141
119
|
};
|
|
142
120
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
namespace CommonGrants.Models;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The base model for a competition.
|
|
5
|
+
*
|
|
6
|
+
* A competition is an application process for a funding opportunity. It often has a
|
|
7
|
+
* distinct application period and set of application forms.
|
|
8
|
+
*/
|
|
9
|
+
@example(Examples.Competition.competition)
|
|
10
|
+
model CompetitionBase {
|
|
11
|
+
/** Globally unique id for the competition */
|
|
12
|
+
id: Types.uuid;
|
|
13
|
+
|
|
14
|
+
/** The opportunity id for the competition */
|
|
15
|
+
opportunityId: Types.uuid;
|
|
16
|
+
|
|
17
|
+
/** The title of the competition */
|
|
18
|
+
title: string;
|
|
19
|
+
|
|
20
|
+
/** The description of the competition */
|
|
21
|
+
description?: string;
|
|
22
|
+
|
|
23
|
+
/** The instructions for the competition */
|
|
24
|
+
instructions?: string | Fields.File[];
|
|
25
|
+
|
|
26
|
+
/** The status of the competition */
|
|
27
|
+
status: CompetitionStatus;
|
|
28
|
+
|
|
29
|
+
/** The key dates in the competition timeline */
|
|
30
|
+
keyDates?: CompetitionTimeline;
|
|
31
|
+
|
|
32
|
+
/** The forms for the competition */
|
|
33
|
+
forms: CompetitionForms;
|
|
34
|
+
|
|
35
|
+
/** Accepted applicant types for the competition */
|
|
36
|
+
acceptedApplicantTypes?: ApplicantType[];
|
|
37
|
+
|
|
38
|
+
/** The custom fields for the competition */
|
|
39
|
+
customFields?: Record<Fields.CustomField>;
|
|
40
|
+
|
|
41
|
+
/** The system metadata for the competition */
|
|
42
|
+
...Fields.SystemMetadata;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// #########################################################
|
|
46
|
+
// CompetitionStatus
|
|
47
|
+
// #########################################################
|
|
48
|
+
|
|
49
|
+
/** The status of the competition */
|
|
50
|
+
@example(Examples.Competition.status)
|
|
51
|
+
model CompetitionStatus {
|
|
52
|
+
/** The status of the competition, from a predefined set of options */
|
|
53
|
+
value: CompetitionStatusOptions;
|
|
54
|
+
|
|
55
|
+
/** A custom value for the status */
|
|
56
|
+
customValue?: string;
|
|
57
|
+
|
|
58
|
+
/** A human-readable description of the status */
|
|
59
|
+
description?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// #########################################################
|
|
63
|
+
// CompetitionStatusOptions
|
|
64
|
+
// #########################################################
|
|
65
|
+
|
|
66
|
+
/** The set of values accepted for competition status
|
|
67
|
+
* - `open`: The competition is open for applications
|
|
68
|
+
* - `closed`: The competition is no longer accepting applications
|
|
69
|
+
* - `custom`: A custom status
|
|
70
|
+
*/
|
|
71
|
+
enum CompetitionStatusOptions {
|
|
72
|
+
open,
|
|
73
|
+
closed,
|
|
74
|
+
custom,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// #########################################################
|
|
78
|
+
// CompetitionForm
|
|
79
|
+
// #########################################################
|
|
80
|
+
|
|
81
|
+
/** Set of forms that need to be completed to apply to the competition. */
|
|
82
|
+
@example(Examples.Competition.forms)
|
|
83
|
+
model CompetitionForms {
|
|
84
|
+
/** The forms for the competition */
|
|
85
|
+
forms: Record<Models.Form>;
|
|
86
|
+
|
|
87
|
+
/** The validation rules for the competition forms */
|
|
88
|
+
validation?: Record<unknown>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// #########################################################
|
|
92
|
+
// CompetitionTimeline
|
|
93
|
+
// #########################################################
|
|
94
|
+
|
|
95
|
+
@example(Examples.Competition.keyDates)
|
|
96
|
+
model CompetitionTimeline {
|
|
97
|
+
/** The start date of the competition */
|
|
98
|
+
openDate?: Fields.Event;
|
|
99
|
+
|
|
100
|
+
/** The end date of the competition */
|
|
101
|
+
closeDate?: Fields.Event;
|
|
102
|
+
|
|
103
|
+
/** The date the competition was created */
|
|
104
|
+
otherDates?: Record<Fields.Event>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// #########################################################
|
|
108
|
+
// Examples
|
|
109
|
+
// #########################################################
|
|
110
|
+
|
|
111
|
+
namespace Examples.Competition {
|
|
112
|
+
const competition = #{
|
|
113
|
+
id: "b7c1e2f4-8a3d-4e2a-9c5b-1f2e3d4c5b6a",
|
|
114
|
+
opportunityId: "b7c1e2f4-8a3d-4e2a-9c5b-1f2e3d4c5b6b",
|
|
115
|
+
title: "Competition 1",
|
|
116
|
+
description: "Competition 1 description",
|
|
117
|
+
instructions: "Competition 1 instructions",
|
|
118
|
+
status: status,
|
|
119
|
+
keyDates: keyDates,
|
|
120
|
+
forms: forms,
|
|
121
|
+
createdAt: utcDateTime.fromISO("2025-01-01T00:00:00Z"),
|
|
122
|
+
lastModifiedAt: utcDateTime.fromISO("2025-01-01T00:00:00Z"),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const keyDates = #{
|
|
126
|
+
openDate: #{
|
|
127
|
+
name: "Open Date",
|
|
128
|
+
eventType: Fields.EventType.singleDate,
|
|
129
|
+
date: Types.isoDate.fromISO("2025-01-01"),
|
|
130
|
+
},
|
|
131
|
+
closeDate: #{
|
|
132
|
+
name: "Close Date",
|
|
133
|
+
eventType: Fields.EventType.singleDate,
|
|
134
|
+
date: Types.isoDate.fromISO("2025-01-30"),
|
|
135
|
+
},
|
|
136
|
+
otherDates: #{
|
|
137
|
+
reviewPeriod: #{
|
|
138
|
+
name: "Application Review Period",
|
|
139
|
+
eventType: Fields.EventType.dateRange,
|
|
140
|
+
startDate: Types.isoDate.fromISO("2025-02-01"),
|
|
141
|
+
endDate: Types.isoDate.fromISO("2025-02-28"),
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const status = #{
|
|
147
|
+
value: CompetitionStatusOptions.open,
|
|
148
|
+
customValue: "custom",
|
|
149
|
+
description: "Competition is open for applications",
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const forms = #{
|
|
153
|
+
forms: #{ formA: Examples.Form.form, formB: Examples.Form.form },
|
|
154
|
+
validation: #{ required: #["formA", "formB"] },
|
|
155
|
+
};
|
|
156
|
+
}
|