@common-grants/core 0.2.0-alpha.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 +58 -45
- package/lib/core/models/applicant-type.tsp +96 -0
- package/lib/core/models/application.tsp +46 -18
- package/lib/core/models/competition.tsp +18 -10
- package/lib/core/models/form-response.tsp +38 -10
- package/lib/core/models/form.tsp +1 -1
- package/lib/core/models/index.tsp +1 -0
- package/lib/core/models/mapping.tsp +45 -55
- package/lib/core/models/opportunity/base.tsp +13 -0
- package/lib/core/models/opportunity/status.tsp +8 -3
- package/lib/core/responses/error.tsp +11 -0
- package/lib/core/routes/applications.tsp +13 -9
- package/lib/core/routes/competitions.tsp +11 -7
- package/lib/core/routes/form-responses.tsp +6 -6
- package/lib/core/routes/forms.tsp +38 -0
- package/lib/core/routes/index.tsp +1 -0
- package/lib/core/routes/opportunities.tsp +14 -6
- package/package.json +1 -1
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
|
@@ -30,16 +30,22 @@ using Versioning;
|
|
|
30
30
|
description: "Endpoints that MUST be implemented by all CommonGrants APIs",
|
|
31
31
|
}
|
|
32
32
|
)
|
|
33
|
+
@tagMetadata("Forms", #{ description: "Endpoints related to forms" })
|
|
33
34
|
@tagMetadata(
|
|
34
35
|
"Applications",
|
|
36
|
+
#{ description: "Endpoints related to applications for a given competition" }
|
|
37
|
+
)
|
|
38
|
+
@tagMetadata(
|
|
39
|
+
"Competitions",
|
|
35
40
|
#{
|
|
36
|
-
description: "Endpoints related to
|
|
41
|
+
description: "Endpoints related to competitions, which are distinct application processes for the same funding opportunity",
|
|
37
42
|
}
|
|
38
43
|
)
|
|
39
44
|
@tagMetadata(
|
|
40
45
|
"Opportunities",
|
|
41
46
|
#{ description: "Endpoints related to funding opportunities" }
|
|
42
47
|
)
|
|
48
|
+
@route("/common-grants") // Prefixes all routes with `/common-grants/`
|
|
43
49
|
namespace CommonGrants.API;
|
|
44
50
|
|
|
45
51
|
// #########################################################
|
|
@@ -47,7 +53,7 @@ namespace CommonGrants.API;
|
|
|
47
53
|
// #########################################################
|
|
48
54
|
|
|
49
55
|
@tag("Opportunities")
|
|
50
|
-
@route("/
|
|
56
|
+
@route("/opportunities")
|
|
51
57
|
namespace Opportunities {
|
|
52
58
|
alias Router = Routes.Opportunities;
|
|
53
59
|
|
|
@@ -61,63 +67,70 @@ namespace Opportunities {
|
|
|
61
67
|
op search is Router.search;
|
|
62
68
|
}
|
|
63
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
|
+
|
|
64
83
|
// #########################################################
|
|
65
84
|
// Applications
|
|
66
85
|
// #########################################################
|
|
67
86
|
|
|
68
87
|
@tag("Applications")
|
|
69
88
|
@tag("experimental")
|
|
70
|
-
@route("/
|
|
71
|
-
namespace
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// #########################################################
|
|
75
|
-
@tag("experimental")
|
|
76
|
-
@route("/competitions")
|
|
77
|
-
namespace DirectApplyWorkflow {
|
|
78
|
-
alias Router = Routes.Competitions;
|
|
79
|
-
|
|
80
|
-
@added(Versions.v0_2)
|
|
81
|
-
op competitionDetails is Router.read;
|
|
82
|
-
|
|
83
|
-
@added(Versions.v0_2)
|
|
84
|
-
op apply is Router.apply;
|
|
85
|
-
}
|
|
89
|
+
@route("/applications")
|
|
90
|
+
namespace Applications {
|
|
91
|
+
alias ApplicationRouter = Routes.Applications;
|
|
92
|
+
alias FormResponseRouter = Routes.FormResponses;
|
|
86
93
|
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
94
|
+
// ################################
|
|
95
|
+
// Start an application workflow
|
|
96
|
+
// ################################
|
|
90
97
|
|
|
91
|
-
@
|
|
92
|
-
|
|
93
|
-
alias ApplicationRouter = Routes.Applications;
|
|
94
|
-
alias FormResponseRouter = Routes.FormResponses;
|
|
98
|
+
@added(Versions.v0_2)
|
|
99
|
+
op startApplication is ApplicationRouter.startApplication;
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// ################################
|
|
101
|
+
@added(Versions.v0_2)
|
|
102
|
+
op getApplication is ApplicationRouter.getApplication;
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
// ################################
|
|
105
|
+
// Update form responses
|
|
106
|
+
// ################################
|
|
102
107
|
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
@added(Versions.v0_2)
|
|
109
|
+
op setFormResponse is FormResponseRouter.setFormResponse;
|
|
105
110
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// ################################
|
|
111
|
+
@added(Versions.v0_2)
|
|
112
|
+
op getFormResponse is FormResponseRouter.getFormResponse;
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
// ################################
|
|
115
|
+
// Submit an application after completing all forms
|
|
116
|
+
// ################################
|
|
112
117
|
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
@added(Versions.v0_2)
|
|
119
|
+
op submitApplication is ApplicationRouter.submitApplication;
|
|
120
|
+
}
|
|
115
121
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
// #########################################################
|
|
123
|
+
// Forms
|
|
124
|
+
// #########################################################
|
|
119
125
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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;
|
|
123
136
|
}
|
|
@@ -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,5 +1,7 @@
|
|
|
1
1
|
namespace CommonGrants.Models;
|
|
2
2
|
|
|
3
|
+
/** The base model for an application to a competition for a funding opportunity */
|
|
4
|
+
@example(Examples.Application.applicationBase)
|
|
3
5
|
model ApplicationBase {
|
|
4
6
|
/** The unique identifier for the application */
|
|
5
7
|
id: Types.uuid;
|
|
@@ -19,6 +21,9 @@ model ApplicationBase {
|
|
|
19
21
|
/** The date and time the application was submitted */
|
|
20
22
|
submittedAt?: utcDateTime | null;
|
|
21
23
|
|
|
24
|
+
/** The validation errors for the application and form responses */
|
|
25
|
+
validationErrors?: Array<unknown>;
|
|
26
|
+
|
|
22
27
|
/** The custom fields about the application */
|
|
23
28
|
customFields?: Record<Fields.CustomField>;
|
|
24
29
|
|
|
@@ -30,16 +35,16 @@ model ApplicationBase {
|
|
|
30
35
|
// AppStatus
|
|
31
36
|
// #########################################################
|
|
32
37
|
|
|
33
|
-
/** The status of the application
|
|
38
|
+
/** The status of the application */
|
|
34
39
|
@example(Examples.Application.submittedStatus)
|
|
35
40
|
model AppStatus {
|
|
36
|
-
/** The status of the application
|
|
41
|
+
/** The status of the application, from a predefined set of options */
|
|
37
42
|
value: AppStatusOptions;
|
|
38
43
|
|
|
39
|
-
/** A custom value for the status
|
|
44
|
+
/** A custom value for the status */
|
|
40
45
|
customValue?: string;
|
|
41
46
|
|
|
42
|
-
/** A description of the status
|
|
47
|
+
/** A human-readable description of the status */
|
|
43
48
|
description?: string;
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -47,21 +52,18 @@ model AppStatus {
|
|
|
47
52
|
// AppStatusOptions
|
|
48
53
|
// #########################################################
|
|
49
54
|
|
|
50
|
-
/** 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
|
+
*/
|
|
51
62
|
enum AppStatusOptions {
|
|
52
|
-
|
|
53
|
-
draft,
|
|
54
|
-
|
|
55
|
-
/** The application has been submitted */
|
|
63
|
+
inProgress,
|
|
56
64
|
submitted,
|
|
57
|
-
|
|
58
|
-
/** The application has been accepted */
|
|
59
65
|
accepted,
|
|
60
|
-
|
|
61
|
-
/** The application has been rejected */
|
|
62
66
|
rejected,
|
|
63
|
-
|
|
64
|
-
/** The application has a custom status */
|
|
65
67
|
custom,
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -69,9 +71,14 @@ enum AppStatusOptions {
|
|
|
69
71
|
// ApplicationFormResponse
|
|
70
72
|
// #########################################################
|
|
71
73
|
|
|
72
|
-
model
|
|
74
|
+
/** The model for a form response included in an application */
|
|
75
|
+
@example(Examples.Application.formResponse)
|
|
76
|
+
model AppFormResponse {
|
|
73
77
|
/** The unique identifier for the application */
|
|
74
78
|
applicationId: Types.uuid;
|
|
79
|
+
|
|
80
|
+
/** Includes all the fields from the FormResponseBase model */
|
|
81
|
+
...FormResponseBase;
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
// #########################################################
|
|
@@ -84,9 +91,30 @@ namespace Examples.Application {
|
|
|
84
91
|
description: "The application has been submitted.",
|
|
85
92
|
};
|
|
86
93
|
|
|
94
|
+
const inProgressStatus = #{
|
|
95
|
+
value: AppStatusOptions.inProgress,
|
|
96
|
+
description: "The application is in progress.",
|
|
97
|
+
};
|
|
98
|
+
|
|
87
99
|
const customStatus = #{
|
|
88
100
|
value: AppStatusOptions.custom,
|
|
89
|
-
customValue: "
|
|
90
|
-
description: "Application
|
|
101
|
+
customValue: "cancelled",
|
|
102
|
+
description: "Application was cancelled before it was submitted.",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const formResponse = #{
|
|
106
|
+
applicationId: "123e4567-e89b-12d3-a456-426614174000",
|
|
107
|
+
...Examples.FormResponse.formResponse,
|
|
108
|
+
};
|
|
109
|
+
|
|
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"),
|
|
91
119
|
};
|
|
92
120
|
}
|
|
@@ -21,17 +21,20 @@ model CompetitionBase {
|
|
|
21
21
|
description?: string;
|
|
22
22
|
|
|
23
23
|
/** The instructions for the competition */
|
|
24
|
-
instructions?: string | Fields.File;
|
|
24
|
+
instructions?: string | Fields.File[];
|
|
25
25
|
|
|
26
26
|
/** The status of the competition */
|
|
27
27
|
status: CompetitionStatus;
|
|
28
28
|
|
|
29
29
|
/** The key dates in the competition timeline */
|
|
30
|
-
keyDates
|
|
30
|
+
keyDates?: CompetitionTimeline;
|
|
31
31
|
|
|
32
32
|
/** The forms for the competition */
|
|
33
33
|
forms: CompetitionForms;
|
|
34
34
|
|
|
35
|
+
/** Accepted applicant types for the competition */
|
|
36
|
+
acceptedApplicantTypes?: ApplicantType[];
|
|
37
|
+
|
|
35
38
|
/** The custom fields for the competition */
|
|
36
39
|
customFields?: Record<Fields.CustomField>;
|
|
37
40
|
|
|
@@ -46,8 +49,13 @@ model CompetitionBase {
|
|
|
46
49
|
/** The status of the competition */
|
|
47
50
|
@example(Examples.Competition.status)
|
|
48
51
|
model CompetitionStatus {
|
|
52
|
+
/** The status of the competition, from a predefined set of options */
|
|
49
53
|
value: CompetitionStatusOptions;
|
|
54
|
+
|
|
55
|
+
/** A custom value for the status */
|
|
50
56
|
customValue?: string;
|
|
57
|
+
|
|
58
|
+
/** A human-readable description of the status */
|
|
51
59
|
description?: string;
|
|
52
60
|
}
|
|
53
61
|
|
|
@@ -55,14 +63,14 @@ model CompetitionStatus {
|
|
|
55
63
|
// CompetitionStatusOptions
|
|
56
64
|
// #########################################################
|
|
57
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
|
+
*/
|
|
58
71
|
enum CompetitionStatusOptions {
|
|
59
|
-
/** The competition is open for applications */
|
|
60
72
|
open,
|
|
61
|
-
|
|
62
|
-
/** The competition is closed for applications */
|
|
63
73
|
closed,
|
|
64
|
-
|
|
65
|
-
/** The competition is in a custom status */
|
|
66
74
|
custom,
|
|
67
75
|
}
|
|
68
76
|
|
|
@@ -77,7 +85,7 @@ model CompetitionForms {
|
|
|
77
85
|
forms: Record<Models.Form>;
|
|
78
86
|
|
|
79
87
|
/** The validation rules for the competition forms */
|
|
80
|
-
validation
|
|
88
|
+
validation?: Record<unknown>;
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
// #########################################################
|
|
@@ -87,10 +95,10 @@ model CompetitionForms {
|
|
|
87
95
|
@example(Examples.Competition.keyDates)
|
|
88
96
|
model CompetitionTimeline {
|
|
89
97
|
/** The start date of the competition */
|
|
90
|
-
openDate
|
|
98
|
+
openDate?: Fields.Event;
|
|
91
99
|
|
|
92
100
|
/** The end date of the competition */
|
|
93
|
-
closeDate
|
|
101
|
+
closeDate?: Fields.Event;
|
|
94
102
|
|
|
95
103
|
/** The date the competition was created */
|
|
96
104
|
otherDates?: Record<Fields.Event>;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
namespace CommonGrants.Models;
|
|
2
2
|
|
|
3
3
|
/** The base model for a form response */
|
|
4
|
+
@example(Examples.FormResponse.formResponse)
|
|
4
5
|
model FormResponseBase {
|
|
5
6
|
/** The unique identifier for the form response */
|
|
6
7
|
id: Types.uuid;
|
|
7
8
|
|
|
8
9
|
/** The form being responded to */
|
|
9
|
-
|
|
10
|
+
formId: Types.uuid;
|
|
10
11
|
|
|
11
12
|
/** The response to the form */
|
|
12
13
|
response: Record<unknown>;
|
|
@@ -15,7 +16,10 @@ model FormResponseBase {
|
|
|
15
16
|
status: FormResponseStatus;
|
|
16
17
|
|
|
17
18
|
/** The validation errors for the form response */
|
|
18
|
-
validationErrors
|
|
19
|
+
validationErrors?: Array<unknown>;
|
|
20
|
+
|
|
21
|
+
/** Custom attributes about the form response */
|
|
22
|
+
customFields?: Record<Fields.CustomField>;
|
|
19
23
|
|
|
20
24
|
/** The system metadata for the form response */
|
|
21
25
|
...Fields.SystemMetadata;
|
|
@@ -28,13 +32,13 @@ model FormResponseBase {
|
|
|
28
32
|
/** The status of the form response */
|
|
29
33
|
@example(Examples.FormResponse.inProgressStatus)
|
|
30
34
|
model FormResponseStatus {
|
|
31
|
-
/** The status of the form response */
|
|
35
|
+
/** The status of the form response, from a predefined set of options */
|
|
32
36
|
value: FormResponseStatusOptions;
|
|
33
37
|
|
|
34
38
|
/** A custom value for the status */
|
|
35
39
|
customValue?: string;
|
|
36
40
|
|
|
37
|
-
/** A description of the status */
|
|
41
|
+
/** A human-readable description of the status */
|
|
38
42
|
description?: string;
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -42,15 +46,15 @@ model FormResponseStatus {
|
|
|
42
46
|
// FormResponseStatusOptions
|
|
43
47
|
// #########################################################
|
|
44
48
|
|
|
45
|
-
/** The
|
|
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
|
+
*/
|
|
46
55
|
enum FormResponseStatusOptions {
|
|
47
|
-
/** The form response has not been started */
|
|
48
56
|
notStarted,
|
|
49
|
-
|
|
50
|
-
/** The form response is in progress */
|
|
51
57
|
inProgress,
|
|
52
|
-
|
|
53
|
-
/** The form response is submitted */
|
|
54
58
|
complete,
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -68,4 +72,28 @@ namespace Examples.FormResponse {
|
|
|
68
72
|
value: FormResponseStatusOptions.complete,
|
|
69
73
|
description: "The form response is complete",
|
|
70
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
|
+
};
|
|
71
99
|
}
|
package/lib/core/models/form.tsp
CHANGED
|
@@ -19,7 +19,7 @@ model Form {
|
|
|
19
19
|
description?: string;
|
|
20
20
|
|
|
21
21
|
/** The form's instructions. */
|
|
22
|
-
instructions?: string | Fields.File;
|
|
22
|
+
instructions?: string | Fields.File[];
|
|
23
23
|
|
|
24
24
|
/** The form's JSON schema used to render the form and validate responses. */
|
|
25
25
|
jsonSchema?: FormJsonSchema;
|
|
@@ -4,7 +4,7 @@ namespace CommonGrants.Models;
|
|
|
4
4
|
// Mapping
|
|
5
5
|
// #########################################################
|
|
6
6
|
|
|
7
|
-
/** A mapping
|
|
7
|
+
/** A mapping format for translating data from one schema to another.
|
|
8
8
|
*
|
|
9
9
|
* Example:
|
|
10
10
|
*
|
|
@@ -16,12 +16,12 @@ namespace CommonGrants.Models;
|
|
|
16
16
|
* "opportunity": {
|
|
17
17
|
* "status": {
|
|
18
18
|
* "switch": {
|
|
19
|
-
* "field": "opportunity_status",
|
|
19
|
+
* "field": "summary.opportunity_status",
|
|
20
20
|
* "case": { "active": "open", "inactive": "closed" },
|
|
21
21
|
* "default": "custom",
|
|
22
22
|
* },
|
|
23
23
|
* },
|
|
24
|
-
* "amount": { "field": "opportunity_amount" },
|
|
24
|
+
* "amount": { "field": "summary.opportunity_amount" },
|
|
25
25
|
* }
|
|
26
26
|
* }
|
|
27
27
|
* ```
|
|
@@ -31,8 +31,10 @@ namespace CommonGrants.Models;
|
|
|
31
31
|
* ```json
|
|
32
32
|
* {
|
|
33
33
|
* "id": "123",
|
|
34
|
-
* "
|
|
35
|
-
*
|
|
34
|
+
* "summary": {
|
|
35
|
+
* "opportunity_status": "active",
|
|
36
|
+
* "opportunity_amount": 100,
|
|
37
|
+
* },
|
|
36
38
|
* }
|
|
37
39
|
* ```
|
|
38
40
|
*
|
|
@@ -43,6 +45,7 @@ namespace CommonGrants.Models;
|
|
|
43
45
|
* "id": "123",
|
|
44
46
|
* "opportunity": { "status": "open", "amount": 100 },
|
|
45
47
|
* }
|
|
48
|
+
* ```
|
|
46
49
|
*/
|
|
47
50
|
@example(Examples.Mapping.flatRenaming)
|
|
48
51
|
@example(Examples.Mapping.nestedRenaming)
|
|
@@ -50,7 +53,7 @@ namespace CommonGrants.Models;
|
|
|
50
53
|
@example(Examples.Mapping.nestedSwitch)
|
|
51
54
|
@example(Examples.Mapping.withLiteralValues)
|
|
52
55
|
model MappingSchema {
|
|
53
|
-
...Record<MappingFunction |
|
|
56
|
+
...Record<MappingFunction | MappingSchema>;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
// #########################################################
|
|
@@ -58,6 +61,9 @@ model MappingSchema {
|
|
|
58
61
|
// #########################################################
|
|
59
62
|
|
|
60
63
|
/** The set of supported mapping functions. */
|
|
64
|
+
@example(Examples.Mapping.constId)
|
|
65
|
+
@example(Examples.Mapping.amountField)
|
|
66
|
+
@example(Examples.Mapping.statusSwitch)
|
|
61
67
|
union MappingFunction {
|
|
62
68
|
`const`: MappingConstantFunction,
|
|
63
69
|
field: MappingFieldFunction,
|
|
@@ -65,10 +71,11 @@ union MappingFunction {
|
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
// #########################################################
|
|
68
|
-
//
|
|
74
|
+
// MappingConstantFunction
|
|
69
75
|
// #########################################################
|
|
70
76
|
|
|
71
77
|
/** Returns a constant value. */
|
|
78
|
+
@example(Examples.Mapping.constId)
|
|
72
79
|
model MappingConstantFunction {
|
|
73
80
|
`const`: unknown;
|
|
74
81
|
}
|
|
@@ -78,6 +85,7 @@ model MappingConstantFunction {
|
|
|
78
85
|
// #########################################################
|
|
79
86
|
|
|
80
87
|
/** Returns the value of a field in the source data. */
|
|
88
|
+
@example(Examples.Mapping.amountField)
|
|
81
89
|
model MappingFieldFunction {
|
|
82
90
|
field: string;
|
|
83
91
|
}
|
|
@@ -87,15 +95,18 @@ model MappingFieldFunction {
|
|
|
87
95
|
// #########################################################
|
|
88
96
|
|
|
89
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)
|
|
90
99
|
model MappingSwitchFunction {
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
switch: {
|
|
101
|
+
/** The field in the source data to switch on. */
|
|
102
|
+
field: string;
|
|
93
103
|
|
|
94
|
-
|
|
95
|
-
|
|
104
|
+
/** An object mapping source field values to desired output values. */
|
|
105
|
+
case: Record<unknown>;
|
|
96
106
|
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
/** The default value to output if no case matches the source field value. */
|
|
108
|
+
default?: unknown;
|
|
109
|
+
};
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
// #########################################################
|
|
@@ -103,60 +114,39 @@ model MappingSwitchFunction {
|
|
|
103
114
|
// #########################################################
|
|
104
115
|
|
|
105
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
|
+
|
|
106
128
|
const flatRenaming = #{
|
|
107
|
-
|
|
108
|
-
|
|
129
|
+
id: constId,
|
|
130
|
+
opportunityStatus: statusField,
|
|
131
|
+
opportunityAmount: amountField,
|
|
109
132
|
};
|
|
110
133
|
|
|
111
134
|
const nestedRenaming = #{
|
|
112
|
-
id:
|
|
113
|
-
opportunity: #{
|
|
114
|
-
status: #{ field: "opportunity_status" },
|
|
115
|
-
amount: #{ field: "opportunity_amount" },
|
|
116
|
-
},
|
|
135
|
+
id: constId,
|
|
136
|
+
opportunity: #{ status: statusField, amount: amountField },
|
|
117
137
|
};
|
|
118
138
|
|
|
119
139
|
const simpleSwitch = #{
|
|
120
|
-
opportunityAmount:
|
|
121
|
-
opportunityStatus:
|
|
122
|
-
switch: #{
|
|
123
|
-
field: "opportunity_status",
|
|
124
|
-
case: #{ active: "open", inactive: "closed" },
|
|
125
|
-
default: "custom",
|
|
126
|
-
},
|
|
127
|
-
},
|
|
140
|
+
opportunityAmount: amountField,
|
|
141
|
+
opportunityStatus: statusSwitch,
|
|
128
142
|
};
|
|
129
143
|
|
|
130
144
|
const nestedSwitch = #{
|
|
131
|
-
opportunity: #{
|
|
132
|
-
status: #{
|
|
133
|
-
switch: #{
|
|
134
|
-
field: "opportunity_status",
|
|
135
|
-
case: #{ active: "open", inactive: "closed" },
|
|
136
|
-
default: "custom",
|
|
137
|
-
},
|
|
138
|
-
amount: #{ field: "opportunity_amount" },
|
|
139
|
-
},
|
|
140
|
-
},
|
|
145
|
+
opportunity: #{ status: statusSwitch, amount: amountField },
|
|
141
146
|
};
|
|
142
147
|
|
|
143
148
|
const withLiteralValues = #{
|
|
144
|
-
id:
|
|
145
|
-
opportunity: #{
|
|
146
|
-
status: #{
|
|
147
|
-
switch: #{
|
|
148
|
-
field: "opportunity_status",
|
|
149
|
-
case: #{ active: "open", inactive: "closed" },
|
|
150
|
-
default: "custom",
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
amount: #{ field: "opportunity_amount" },
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const formToCommonGrants = #{
|
|
158
|
-
name: #{ field: "name" },
|
|
159
|
-
email: #{ field: "email" },
|
|
160
|
-
phone: #{ field: "phone" },
|
|
149
|
+
id: constId,
|
|
150
|
+
opportunity: #{ status: statusSwitch, amount: amountField },
|
|
161
151
|
};
|
|
162
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 */
|
|
@@ -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
|
+
}
|
|
@@ -20,7 +20,7 @@ interface Applications {
|
|
|
20
20
|
// ##############################
|
|
21
21
|
|
|
22
22
|
@summary("Start an application")
|
|
23
|
-
@doc("Start a
|
|
23
|
+
@doc("Start a new application for a given competition.")
|
|
24
24
|
@post
|
|
25
25
|
@route("/start")
|
|
26
26
|
startApplication(
|
|
@@ -39,12 +39,12 @@ interface Applications {
|
|
|
39
39
|
// ###############################
|
|
40
40
|
|
|
41
41
|
@summary("View an application")
|
|
42
|
-
@doc("View an application for a given competition with
|
|
42
|
+
@doc("View an application for a given competition, along with its form responses and validation errors.")
|
|
43
43
|
@get
|
|
44
|
-
@route("/{
|
|
44
|
+
@route("/{appId}")
|
|
45
45
|
getApplication(
|
|
46
46
|
/** The ID of the application to get */
|
|
47
|
-
@path
|
|
47
|
+
@path appId: Types.uuid,
|
|
48
48
|
): Responses.Ok<Models.ApplicationBase> | Responses.NotFound | Responses.Unauthorized;
|
|
49
49
|
|
|
50
50
|
// ###############################
|
|
@@ -52,11 +52,15 @@ interface Applications {
|
|
|
52
52
|
// ###############################
|
|
53
53
|
|
|
54
54
|
@summary("Submit an application")
|
|
55
|
-
@doc("Submit an application
|
|
56
|
-
@
|
|
57
|
-
@route("/{
|
|
55
|
+
@doc("Submit an application to a competition. Applications that have validation errors will be blocked from submitting until the errors are fixed.")
|
|
56
|
+
@put
|
|
57
|
+
@route("/{appId}/submit")
|
|
58
58
|
submitApplication(
|
|
59
59
|
/** The ID of the application to submit */
|
|
60
|
-
@path
|
|
61
|
-
):
|
|
60
|
+
@path appId: Types.uuid,
|
|
61
|
+
):
|
|
62
|
+
| Responses.Ok<unknown>
|
|
63
|
+
| Responses.ApplicationSubmissionError
|
|
64
|
+
| Responses.NotFound
|
|
65
|
+
| Responses.Unauthorized;
|
|
62
66
|
}
|
|
@@ -19,13 +19,17 @@ interface Competitions {
|
|
|
19
19
|
// View competition details
|
|
20
20
|
// ###############################
|
|
21
21
|
|
|
22
|
+
/** View details about a competition for a given funding opportunity.
|
|
23
|
+
*
|
|
24
|
+
* Each competition may have a distinct set of forms, be limited to a
|
|
25
|
+
* certain types of applicants, or have a different application period.
|
|
26
|
+
*/
|
|
22
27
|
@summary("View competition details")
|
|
23
|
-
@doc("View additional details about a competition for a given opportunity. A competition is an application process for a funding opportunity, often with a distinct set of forms and key dates.")
|
|
24
28
|
@get
|
|
25
|
-
@route("/
|
|
29
|
+
@route("/{compId}")
|
|
26
30
|
read(
|
|
27
|
-
/** The ID of the competition to
|
|
28
|
-
@path
|
|
31
|
+
/** The ID of the competition to view */
|
|
32
|
+
@path compId: Types.uuid,
|
|
29
33
|
): Responses.Ok<Models.CompetitionBase> | Responses.NotFound;
|
|
30
34
|
|
|
31
35
|
// ###############################
|
|
@@ -33,12 +37,12 @@ interface Competitions {
|
|
|
33
37
|
// ###############################
|
|
34
38
|
|
|
35
39
|
@summary("Apply to a competition")
|
|
36
|
-
@doc("Apply to a given competition with all of the required information")
|
|
40
|
+
@doc("Apply to a given competition with all of the required information.")
|
|
37
41
|
@post
|
|
38
|
-
@route("/
|
|
42
|
+
@route("/{compId}/apply")
|
|
39
43
|
apply(
|
|
40
44
|
/** The ID of the competition to apply to */
|
|
41
|
-
@path
|
|
45
|
+
@path compId: Types.uuid,
|
|
42
46
|
|
|
43
47
|
/** The application to apply to the competition */
|
|
44
48
|
@body application: Models.ApplicationBase,
|
|
@@ -20,14 +20,14 @@ interface FormResponses {
|
|
|
20
20
|
// ###############################
|
|
21
21
|
|
|
22
22
|
@summary("Respond to a form")
|
|
23
|
-
@doc("
|
|
23
|
+
@doc("Set or update the response to a given form in an application.")
|
|
24
24
|
@put
|
|
25
25
|
@route("/{appId}/forms/{formId}")
|
|
26
26
|
setFormResponse(
|
|
27
|
-
/** The ID of the application to
|
|
27
|
+
/** The ID of the application to whose form response is being updated */
|
|
28
28
|
@path appId: Types.uuid,
|
|
29
29
|
|
|
30
|
-
/** The ID of the form
|
|
30
|
+
/** The ID of the form whose response is being updated */
|
|
31
31
|
@path formId: Types.uuid,
|
|
32
32
|
|
|
33
33
|
/** The response to the form */
|
|
@@ -39,14 +39,14 @@ interface FormResponses {
|
|
|
39
39
|
// ###############################
|
|
40
40
|
|
|
41
41
|
@summary("Get a form response")
|
|
42
|
-
@doc("Get the response to a given form
|
|
42
|
+
@doc("Get the response to a given form in an application.")
|
|
43
43
|
@get
|
|
44
44
|
@route("/{appId}/forms/{formId}")
|
|
45
45
|
getFormResponse(
|
|
46
|
-
/** The ID of the application to
|
|
46
|
+
/** The ID of the application to whose form response is being retrieved */
|
|
47
47
|
@path appId: Types.uuid,
|
|
48
48
|
|
|
49
|
-
/** The ID of the form
|
|
49
|
+
/** The ID of the form whose response is being retrieved */
|
|
50
50
|
@path formId: Types.uuid,
|
|
51
51
|
): Responses.Ok<Models.AppFormResponse>;
|
|
52
52
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "../responses/index.tsp";
|
|
2
|
+
|
|
3
|
+
// Define the top-level namespace for CommonGrants routes
|
|
4
|
+
namespace CommonGrants.Routes;
|
|
5
|
+
|
|
6
|
+
// Expose the contents of the Http and Rest namespaces
|
|
7
|
+
// these include the decorators @route, @get, etc.
|
|
8
|
+
using TypeSpec.Http;
|
|
9
|
+
|
|
10
|
+
/** A re-usable interface for an Applications router
|
|
11
|
+
*
|
|
12
|
+
* To implement this interface, we recommend declaring a namespace,
|
|
13
|
+
* instantiating the router using `alias` (instead of `extends`),
|
|
14
|
+
* and decorating the namespace with `@route` and `@tag` since they aren't
|
|
15
|
+
* inherited directly from the interface.
|
|
16
|
+
*/
|
|
17
|
+
interface Forms {
|
|
18
|
+
// ###############################
|
|
19
|
+
// Update form response
|
|
20
|
+
// ###############################
|
|
21
|
+
|
|
22
|
+
@summary("List forms")
|
|
23
|
+
@doc("Get a paginated list of forms, sorted by `lastModifiedAt` with most recent first.")
|
|
24
|
+
@get
|
|
25
|
+
list(...Pagination.PaginatedQueryParams): Responses.Paginated<Models.Form>;
|
|
26
|
+
|
|
27
|
+
// ###############################
|
|
28
|
+
// View form details
|
|
29
|
+
// ###############################
|
|
30
|
+
|
|
31
|
+
@summary("View form details")
|
|
32
|
+
@doc("View details about a given form.")
|
|
33
|
+
@get
|
|
34
|
+
read(
|
|
35
|
+
/** The ID of the form to view */
|
|
36
|
+
@path formId: Types.uuid,
|
|
37
|
+
): Responses.Ok<Models.Form>;
|
|
38
|
+
}
|
|
@@ -8,6 +8,7 @@ namespace CommonGrants.Routes;
|
|
|
8
8
|
// Expose the contents of the Http and Rest namespaces
|
|
9
9
|
// these include the decorators @route, @get, etc.
|
|
10
10
|
using TypeSpec.Http;
|
|
11
|
+
using TypeSpec.Versioning;
|
|
11
12
|
|
|
12
13
|
/** A re-usable interface for an Opportunities router
|
|
13
14
|
*
|
|
@@ -52,7 +53,7 @@ interface Opportunities {
|
|
|
52
53
|
): Responses.Paginated<T>;
|
|
53
54
|
|
|
54
55
|
// ##############################
|
|
55
|
-
// View an opportunity
|
|
56
|
+
// View an opportunity (v0.1.0)
|
|
56
57
|
// ##############################
|
|
57
58
|
|
|
58
59
|
/** `GET /opportunities/<id>` View opportunity details
|
|
@@ -60,12 +61,19 @@ interface Opportunities {
|
|
|
60
61
|
* @template T Type of the response model.
|
|
61
62
|
* Must be an extension of Schemas.OpportunityBase. Default is Schemas.OpportunityBase.
|
|
62
63
|
*/
|
|
63
|
-
@summary("View opportunity")
|
|
64
|
-
@doc("View
|
|
64
|
+
@summary("View opportunity details")
|
|
65
|
+
@doc("View details about an opportunity.")
|
|
65
66
|
@get
|
|
66
|
-
read<T extends Models.OpportunityBase = Models.
|
|
67
|
+
read<T extends Models.OpportunityBase = Models.OpportunityDetails>(
|
|
67
68
|
/** The ID of the opportunity to view */
|
|
68
|
-
@path
|
|
69
|
+
@path
|
|
70
|
+
@removed(Versions.v0_2)
|
|
71
|
+
id: Types.uuid,
|
|
72
|
+
|
|
73
|
+
/** The ID of the opportunity to view */
|
|
74
|
+
@path
|
|
75
|
+
@added(Versions.v0_2)
|
|
76
|
+
oppId: Types.uuid,
|
|
69
77
|
): Responses.Ok<T> | Responses.NotFound;
|
|
70
78
|
|
|
71
79
|
// ###############################
|
|
@@ -78,7 +86,7 @@ interface Opportunities {
|
|
|
78
86
|
* Must be an extension of Schemas.OpportunityBase. Default is Schemas.OpportunityBase.
|
|
79
87
|
*/
|
|
80
88
|
@summary("Search opportunities")
|
|
81
|
-
@doc("Search for opportunities based on the provided filters")
|
|
89
|
+
@doc("Search for opportunities based on the provided filters.")
|
|
82
90
|
@post
|
|
83
91
|
@route("/search")
|
|
84
92
|
search<T extends Models.OpportunityBase = Models.OpportunityBase>(
|