@nativesquare/upwork 0.1.1 → 0.2.1
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 +150 -94
- package/dist/client/index.d.ts +6 -13
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +34 -22
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +7 -8
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/public.d.ts +27 -8
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +109 -18
- package/dist/component/public.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +40 -38
- package/src/component/_generated/component.ts +17 -13
- package/src/component/public.ts +139 -18
package/README.md
CHANGED
|
@@ -1,141 +1,192 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Upwork Component for Convex
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/@nativesquare%2Fupwork)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<!-- START: Include on https://convex.dev/components -->
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
your component
|
|
12
|
-
3. Write example usage in example/convex/example.ts.
|
|
13
|
-
4. Delete the text in this readme until `---` and flesh out the README.
|
|
14
|
-
5. Publish to npm with `pnpm run alpha` or `pnpm run release`.
|
|
7
|
+
A [Convex component](https://convex.dev/components) that integrates the
|
|
8
|
+
[Upwork API](https://developers.upwork.com/) into your Convex app. Search job
|
|
9
|
+
postings, fetch individual listings, and manage OAuth authentication — all with
|
|
10
|
+
built-in caching that complies with Upwork's API terms.
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
```ts
|
|
13
|
+
const upwork = new Upwork(components.upwork);
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
pnpm run dev
|
|
21
|
-
```
|
|
15
|
+
// Search jobs
|
|
16
|
+
const results = await upwork.searchJobPostings(ctx, { searchQuery: "react" });
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
// Get a single posting (cache-first, fetches from API if not cached)
|
|
19
|
+
const posting = await upwork.getJobPosting(ctx, { upworkId: "~01abc123" });
|
|
20
|
+
```
|
|
26
21
|
|
|
27
|
-
|
|
22
|
+
Found a bug? Feature request?
|
|
23
|
+
[File it here](https://github.com/NativeSquare/upwork/issues).
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
## Prerequisites
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
If you will be adding frontend code, add a peer dependency on React in
|
|
34
|
-
package.json.
|
|
27
|
+
You'll need an Upwork API application. Create one at the
|
|
28
|
+
[Upwork Developer Portal](https://www.upwork.com/developer/keys/apply).
|
|
35
29
|
|
|
36
|
-
|
|
30
|
+
## Installation
|
|
37
31
|
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
├── README.md documentation of your component
|
|
41
|
-
├── package.json component name, version number, other metadata
|
|
42
|
-
├── pnpm-lock.yaml Components are like libraries, pnpm-lock.yaml
|
|
43
|
-
│ is .gitignored and ignored by consumers.
|
|
44
|
-
├── src
|
|
45
|
-
│ ├── component/
|
|
46
|
-
│ │ ├── _generated/ Files here are generated for the component.
|
|
47
|
-
│ │ ├── convex.config.ts Name your component here and use other components
|
|
48
|
-
│ │ ├── lib.ts Define functions here and in new files in this directory
|
|
49
|
-
│ │ └── schema.ts schema specific to this component
|
|
50
|
-
│ ├── client/
|
|
51
|
-
│ │ └── index.ts Code that needs to run in the app that uses the
|
|
52
|
-
│ │ component. Generally the app interacts directly with
|
|
53
|
-
│ │ the component's exposed API (src/component/*).
|
|
54
|
-
│ └── react/ Code intended to be used on the frontend goes here.
|
|
55
|
-
│ │ Your are free to delete this if this component
|
|
56
|
-
│ │ does not provide code.
|
|
57
|
-
│ └── index.ts
|
|
58
|
-
├── example/ example Convex app that uses this component
|
|
59
|
-
│ └── convex/
|
|
60
|
-
│ ├── _generated/ Files here are generated for the example app.
|
|
61
|
-
│ ├── convex.config.ts Imports and uses this component
|
|
62
|
-
│ ├── myFunctions.ts Functions that use the component
|
|
63
|
-
│ └── schema.ts Example app schema
|
|
64
|
-
└── dist/ Publishing artifacts will be created here.
|
|
32
|
+
```sh
|
|
33
|
+
npm install @nativesquare/upwork
|
|
65
34
|
```
|
|
66
35
|
|
|
67
|
-
|
|
36
|
+
Register the component in your `convex/convex.config.ts`:
|
|
68
37
|
|
|
69
|
-
|
|
38
|
+
```ts
|
|
39
|
+
// convex/convex.config.ts
|
|
40
|
+
import { defineApp } from "convex/server";
|
|
41
|
+
import upwork from "@nativesquare/upwork/convex.config.js";
|
|
70
42
|
|
|
71
|
-
|
|
43
|
+
const app = defineApp();
|
|
44
|
+
app.use(upwork);
|
|
72
45
|
|
|
73
|
-
|
|
46
|
+
export default app;
|
|
47
|
+
```
|
|
74
48
|
|
|
75
|
-
|
|
76
|
-
- [ ] Why should you use this component?
|
|
77
|
-
- [ ] Links to docs / other resources?
|
|
49
|
+
## Environment Variables
|
|
78
50
|
|
|
79
|
-
|
|
80
|
-
[File it here](https://github.com/NativeSquare/my-component/issues).
|
|
51
|
+
Set the following environment variables in your Convex deployment:
|
|
81
52
|
|
|
82
|
-
|
|
53
|
+
| Variable | Description |
|
|
54
|
+
| ---------------------- | ----------------------------------------- |
|
|
55
|
+
| `UPWORK_CLIENT_ID` | Your Upwork API application client ID |
|
|
56
|
+
| `UPWORK_CLIENT_SECRET` | Your Upwork API application client secret |
|
|
57
|
+
| `CONVEX_SITE_URL` | Your Convex deployment's HTTP Actions URL |
|
|
58
|
+
|
|
59
|
+
The component reads these automatically — you never need to pass credentials
|
|
60
|
+
in your code.
|
|
61
|
+
|
|
62
|
+
## Setup
|
|
63
|
+
|
|
64
|
+
### 1. Register HTTP routes
|
|
83
65
|
|
|
84
|
-
|
|
85
|
-
|
|
66
|
+
The component needs an HTTP route to handle the OAuth callback from Upwork.
|
|
67
|
+
Add this to your `convex/http.ts`:
|
|
86
68
|
|
|
87
69
|
```ts
|
|
88
|
-
// convex/
|
|
89
|
-
import {
|
|
90
|
-
import
|
|
70
|
+
// convex/http.ts
|
|
71
|
+
import { httpRouter } from "convex/server";
|
|
72
|
+
import { registerRoutes } from "@nativesquare/upwork";
|
|
73
|
+
import { components } from "./_generated/api";
|
|
91
74
|
|
|
92
|
-
const
|
|
93
|
-
app.use(myComponent);
|
|
75
|
+
const http = httpRouter();
|
|
94
76
|
|
|
95
|
-
|
|
77
|
+
registerRoutes(http, components.upwork, {
|
|
78
|
+
onSuccess: "http://localhost:5173", // redirect after successful auth
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export default http;
|
|
96
82
|
```
|
|
97
83
|
|
|
98
|
-
|
|
84
|
+
The `onSuccess` option is where the user is redirected after connecting their
|
|
85
|
+
Upwork account. If omitted, a plain text success message is shown instead.
|
|
86
|
+
|
|
87
|
+
### 2. Create the client
|
|
99
88
|
|
|
100
89
|
```ts
|
|
90
|
+
// convex/example.ts
|
|
91
|
+
import { action, query } from "./_generated/server";
|
|
101
92
|
import { components } from "./_generated/api";
|
|
93
|
+
import { Upwork } from "@nativesquare/upwork";
|
|
94
|
+
|
|
95
|
+
const upwork = new Upwork(components.upwork);
|
|
96
|
+
```
|
|
102
97
|
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
## API Reference
|
|
99
|
+
|
|
100
|
+
### `getAuthorizationUrl()`
|
|
101
|
+
|
|
102
|
+
Returns the Upwork OAuth authorization URL. Redirect users here to connect
|
|
103
|
+
their Upwork account.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
export const getAuthUrl = query({
|
|
107
|
+
args: {},
|
|
108
|
+
handler: async () => {
|
|
109
|
+
return upwork.getAuthorizationUrl();
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `getAuthStatus(ctx)`
|
|
115
|
+
|
|
116
|
+
Returns the current OAuth connection status: `"connected"`, `"disconnected"`,
|
|
117
|
+
or `"expired"`.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
export const authStatus = query({
|
|
121
|
+
args: {},
|
|
122
|
+
handler: async (ctx) => {
|
|
123
|
+
return await upwork.getAuthStatus(ctx);
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### `searchJobPostings(ctx, opts?)`
|
|
129
|
+
|
|
130
|
+
Searches the Upwork marketplace and caches the results. Requires an action
|
|
131
|
+
context since it makes a live API call.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
export const search = action({
|
|
135
|
+
args: { searchQuery: v.optional(v.string()) },
|
|
105
136
|
handler: async (ctx, args) => {
|
|
106
|
-
return await
|
|
107
|
-
|
|
137
|
+
return await upwork.searchJobPostings(ctx, {
|
|
138
|
+
searchQuery: args.searchQuery,
|
|
139
|
+
sortField: "RECENCY", // optional, defaults to "RECENCY"
|
|
108
140
|
});
|
|
109
141
|
},
|
|
110
142
|
});
|
|
111
143
|
```
|
|
112
144
|
|
|
113
|
-
|
|
145
|
+
Returns `{ totalCount, postings, hasNextPage }`.
|
|
114
146
|
|
|
115
|
-
###
|
|
147
|
+
### `getJobPosting(ctx, opts)`
|
|
116
148
|
|
|
117
|
-
|
|
149
|
+
Gets a single job posting by its Upwork ID. Uses a hybrid strategy: checks the
|
|
150
|
+
local cache first, and if not found, fetches from the Upwork API and stores the
|
|
151
|
+
result. Requires an action context.
|
|
118
152
|
|
|
119
153
|
```ts
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
154
|
+
export const getJob = action({
|
|
155
|
+
args: { upworkId: v.string() },
|
|
156
|
+
handler: async (ctx, args) => {
|
|
157
|
+
return await upwork.getJobPosting(ctx, { upworkId: args.upworkId });
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
```
|
|
123
161
|
|
|
124
|
-
|
|
162
|
+
Returns a `JobPosting` or `null` if the posting doesn't exist.
|
|
125
163
|
|
|
126
|
-
|
|
127
|
-
pathPrefix: "/my-component",
|
|
128
|
-
});
|
|
164
|
+
### `listJobPostings(ctx, opts?)`
|
|
129
165
|
|
|
130
|
-
|
|
166
|
+
Lists cached job postings from the database. This is a query (no API call),
|
|
167
|
+
so it's reactive and fast.
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
export const list = query({
|
|
171
|
+
args: { limit: v.optional(v.number()) },
|
|
172
|
+
handler: async (ctx, args) => {
|
|
173
|
+
return await upwork.listJobPostings(ctx, { limit: args.limit });
|
|
174
|
+
},
|
|
175
|
+
});
|
|
131
176
|
```
|
|
132
177
|
|
|
133
|
-
|
|
178
|
+
Returns up to `limit` postings (default 50) cached within the last 23 hours.
|
|
179
|
+
|
|
180
|
+
### `exchangeAuthCode(ctx, opts)`
|
|
181
|
+
|
|
182
|
+
Exchanges an OAuth authorization code for access tokens. You typically don't
|
|
183
|
+
need to call this directly — `registerRoutes` handles it via the callback
|
|
184
|
+
endpoint.
|
|
134
185
|
|
|
135
186
|
## Upwork API Compliance
|
|
136
187
|
|
|
137
188
|
This component caches job postings from the Upwork API to improve performance
|
|
138
|
-
and reduce API calls.
|
|
189
|
+
and reduce API calls. It is designed to comply with
|
|
139
190
|
[Upwork's API Terms of Use](https://www.upwork.com/legal#api):
|
|
140
191
|
|
|
141
192
|
- **24-hour caching limit**: Upwork does not allow storing API data for more
|
|
@@ -145,16 +196,21 @@ and reduce API calls. Users of this component must comply with
|
|
|
145
196
|
|
|
146
197
|
- **Rate limiting**: The Upwork API enforces a rate limit of **300 requests per
|
|
147
198
|
minute per IP address**. Exceeding this limit will result in HTTP 429
|
|
148
|
-
"Too Many Requests" responses. The caching layer
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
`
|
|
199
|
+
"Too Many Requests" responses. The caching layer helps you stay within these
|
|
200
|
+
limits — cached data is served directly from the database without hitting the
|
|
201
|
+
Upwork API. Be mindful of how frequently you call `searchJobPostings` and
|
|
202
|
+
`getJobPosting`, as they may make live API requests.
|
|
203
|
+
|
|
204
|
+
- **Token refresh**: Access tokens are automatically refreshed when expired.
|
|
152
205
|
|
|
153
206
|
<!-- END: Include on https://convex.dev/components -->
|
|
154
207
|
|
|
155
|
-
|
|
208
|
+
## Development
|
|
156
209
|
|
|
157
210
|
```sh
|
|
158
211
|
pnpm i
|
|
159
212
|
pnpm run dev
|
|
160
213
|
```
|
|
214
|
+
|
|
215
|
+
See the [example app](./example/convex/example.ts) for a complete working
|
|
216
|
+
integration.
|
package/dist/client/index.d.ts
CHANGED
|
@@ -5,30 +5,23 @@ export type UpworkComponent = ComponentApi;
|
|
|
5
5
|
export declare class Upwork {
|
|
6
6
|
component: UpworkComponent;
|
|
7
7
|
constructor(component: UpworkComponent);
|
|
8
|
-
getAuthorizationUrl(
|
|
9
|
-
clientId: string;
|
|
10
|
-
siteUrl: string;
|
|
11
|
-
}): string;
|
|
8
|
+
getAuthorizationUrl(): string;
|
|
12
9
|
exchangeAuthCode(ctx: ActionCtx, opts: {
|
|
13
|
-
clientId: string;
|
|
14
|
-
clientSecret: string;
|
|
15
10
|
code: string;
|
|
16
|
-
siteUrl: string;
|
|
17
11
|
}): Promise<void>;
|
|
18
|
-
searchJobPostings(ctx: ActionCtx, opts
|
|
19
|
-
clientId: string;
|
|
20
|
-
clientSecret: string;
|
|
12
|
+
searchJobPostings(ctx: ActionCtx, opts?: {
|
|
21
13
|
searchQuery?: string;
|
|
22
14
|
sortField?: string;
|
|
23
15
|
}): Promise<SearchResult>;
|
|
16
|
+
getJobPosting(ctx: ActionCtx, opts: {
|
|
17
|
+
upworkId: string;
|
|
18
|
+
}): Promise<JobPosting | null>;
|
|
24
19
|
listJobPostings(ctx: QueryCtx, opts?: {
|
|
25
20
|
limit?: number;
|
|
26
21
|
}): Promise<JobPosting[]>;
|
|
27
22
|
getAuthStatus(ctx: QueryCtx): Promise<AuthStatus>;
|
|
28
23
|
}
|
|
29
|
-
export declare function registerRoutes(http: HttpRouter, component: UpworkComponent, opts
|
|
30
|
-
clientId: string;
|
|
31
|
-
clientSecret: string;
|
|
24
|
+
export declare function registerRoutes(http: HttpRouter, component: UpworkComponent, opts?: {
|
|
32
25
|
onSuccess?: string;
|
|
33
26
|
}): void;
|
|
34
27
|
export { CALLBACK_PATH } from "./types.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAKhD,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC;AAE3C,qBAAa,MAAM;IACE,SAAS,EAAE,eAAe;gBAA1B,SAAS,EAAE,eAAe;IAE7C,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAKhD,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC;AAE3C,qBAAa,MAAM;IACE,SAAS,EAAE,eAAe;gBAA1B,SAAS,EAAE,eAAe;IAE7C,mBAAmB,IAAI,MAAM;IAkBvB,gBAAgB,CACpB,GAAG,EAAE,SAAS,EACd,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GACrB,OAAO,CAAC,IAAI,CAAC;IAYV,iBAAiB,CACrB,GAAG,EAAE,SAAS,EACd,IAAI,CAAC,EAAE;QACL,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACA,OAAO,CAAC,YAAY,CAAC;IAOlB,aAAa,CACjB,GAAG,EAAE,SAAS,EACd,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GACzB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAgBvB,eAAe,CACnB,GAAG,EAAE,QAAQ,EACb,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACxB,OAAO,CAAC,UAAU,EAAE,CAAC;IAMlB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;CAGxD;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,eAAe,EAC1B,IAAI,CAAC,EAAE;IACL,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,QAmCF;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/client/index.js
CHANGED
|
@@ -6,30 +6,53 @@ export class Upwork {
|
|
|
6
6
|
constructor(component) {
|
|
7
7
|
this.component = component;
|
|
8
8
|
}
|
|
9
|
-
getAuthorizationUrl(
|
|
10
|
-
const
|
|
9
|
+
getAuthorizationUrl() {
|
|
10
|
+
const clientId = process.env.UPWORK_CLIENT_ID;
|
|
11
|
+
const siteUrl = process.env.CONVEX_SITE_URL;
|
|
12
|
+
if (!clientId) {
|
|
13
|
+
throw new Error("Missing UPWORK_CLIENT_ID environment variable.");
|
|
14
|
+
}
|
|
15
|
+
if (!siteUrl) {
|
|
16
|
+
throw new Error("Missing CONVEX_SITE_URL environment variable.");
|
|
17
|
+
}
|
|
18
|
+
const redirectUri = `${siteUrl}${CALLBACK_PATH}`;
|
|
11
19
|
const params = new URLSearchParams({
|
|
12
20
|
response_type: "code",
|
|
13
|
-
client_id:
|
|
21
|
+
client_id: clientId,
|
|
14
22
|
redirect_uri: redirectUri,
|
|
15
23
|
});
|
|
16
24
|
return `${BASE_URL}/ab/account-security/oauth2/authorize?${params.toString()}`;
|
|
17
25
|
}
|
|
18
26
|
async exchangeAuthCode(ctx, opts) {
|
|
19
|
-
const
|
|
27
|
+
const siteUrl = process.env.CONVEX_SITE_URL;
|
|
28
|
+
if (!siteUrl) {
|
|
29
|
+
throw new Error("Missing CONVEX_SITE_URL environment variable.");
|
|
30
|
+
}
|
|
31
|
+
const redirectUri = `${siteUrl}${CALLBACK_PATH}`;
|
|
20
32
|
await ctx.runAction(this.component.public.exchangeAuthCode, {
|
|
21
|
-
clientId: opts.clientId,
|
|
22
|
-
clientSecret: opts.clientSecret,
|
|
23
33
|
code: opts.code,
|
|
24
34
|
redirectUri,
|
|
25
35
|
});
|
|
26
36
|
}
|
|
27
37
|
async searchJobPostings(ctx, opts) {
|
|
28
38
|
return await ctx.runAction(this.component.public.searchJobPostings, {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
searchQuery: opts?.searchQuery,
|
|
40
|
+
sortField: opts?.sortField,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async getJobPosting(ctx, opts) {
|
|
44
|
+
const cached = await ctx.runQuery(this.component.public.getJobPosting, {
|
|
45
|
+
upworkId: opts.upworkId,
|
|
46
|
+
});
|
|
47
|
+
if (cached)
|
|
48
|
+
return cached;
|
|
49
|
+
const fetched = await ctx.runAction(this.component.public.fetchJobPosting, {
|
|
50
|
+
upworkId: opts.upworkId,
|
|
51
|
+
});
|
|
52
|
+
if (!fetched)
|
|
53
|
+
return null;
|
|
54
|
+
return await ctx.runQuery(this.component.public.getJobPosting, {
|
|
55
|
+
upworkId: opts.upworkId,
|
|
33
56
|
});
|
|
34
57
|
}
|
|
35
58
|
async listJobPostings(ctx, opts) {
|
|
@@ -42,15 +65,6 @@ export class Upwork {
|
|
|
42
65
|
}
|
|
43
66
|
}
|
|
44
67
|
export function registerRoutes(http, component, opts) {
|
|
45
|
-
const missing = [];
|
|
46
|
-
if (!opts.clientId)
|
|
47
|
-
missing.push("clientId");
|
|
48
|
-
if (!opts.clientSecret)
|
|
49
|
-
missing.push("clientSecret");
|
|
50
|
-
if (missing.length > 0) {
|
|
51
|
-
throw new Error(`registerRoutes: missing required options: ${missing.join(", ")}. ` +
|
|
52
|
-
`Make sure UPWORK_CLIENT_ID and UPWORK_CLIENT_SECRET environment variables are set.`);
|
|
53
|
-
}
|
|
54
68
|
http.route({
|
|
55
69
|
path: CALLBACK_PATH,
|
|
56
70
|
method: "GET",
|
|
@@ -63,8 +77,6 @@ export function registerRoutes(http, component, opts) {
|
|
|
63
77
|
const redirectUri = `${url.origin}${url.pathname}`;
|
|
64
78
|
try {
|
|
65
79
|
await ctx.runAction(component.public.exchangeAuthCode, {
|
|
66
|
-
clientId: opts.clientId,
|
|
67
|
-
clientSecret: opts.clientSecret,
|
|
68
80
|
code,
|
|
69
81
|
redirectUri,
|
|
70
82
|
});
|
|
@@ -73,7 +85,7 @@ export function registerRoutes(http, component, opts) {
|
|
|
73
85
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
74
86
|
return new Response(`OAuth callback failed: ${message}`, { status: 500 });
|
|
75
87
|
}
|
|
76
|
-
if (opts
|
|
88
|
+
if (opts?.onSuccess) {
|
|
77
89
|
return new Response(null, {
|
|
78
90
|
status: 302,
|
|
79
91
|
headers: { Location: opts.onSuccess },
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,QAAQ,GAAG,yCAAyC,CAAC;AAI3D,MAAM,OAAO,MAAM;IACE;IAAnB,YAAmB,SAA0B;QAA1B,cAAS,GAAT,SAAS,CAAiB;IAAG,CAAC;IAEjD,mBAAmB,CAAC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,QAAQ,GAAG,yCAAyC,CAAC;AAI3D,MAAM,OAAO,MAAM;IACE;IAAnB,YAAmB,SAA0B;QAA1B,cAAS,GAAT,SAAS,CAAiB;IAAG,CAAC;IAEjD,mBAAmB;QACjB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;QAC9C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAC5C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,WAAW,GAAG,GAAG,OAAO,GAAG,aAAa,EAAE,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YACjC,aAAa,EAAE,MAAM;YACrB,SAAS,EAAE,QAAQ;YACnB,YAAY,EAAE,WAAW;SAC1B,CAAC,CAAC;QACH,OAAO,GAAG,QAAQ,yCAAyC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,gBAAgB,CACpB,GAAc,EACd,IAAsB;QAEtB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,WAAW,GAAG,GAAG,OAAO,GAAG,aAAa,EAAE,CAAC;QACjD,MAAM,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,gBAAgB,EAAE;YAC1D,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,WAAW;SACZ,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,iBAAiB,CACrB,GAAc,EACd,IAGC;QAED,OAAO,MAAM,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,iBAAiB,EAAE;YAClE,WAAW,EAAE,IAAI,EAAE,WAAW;YAC9B,SAAS,EAAE,IAAI,EAAE,SAAS;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,GAAc,EACd,IAA0B;QAE1B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,aAAa,EAAE;YACrE,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;QACH,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,EAAE;YACzE,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;QACH,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAE1B,OAAO,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,aAAa,EAAE;YAC7D,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,eAAe,CACnB,GAAa,EACb,IAAyB;QAEzB,OAAO,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,EAAE;YAC/D,KAAK,EAAE,IAAI,EAAE,KAAK;SACnB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,GAAa;QAC/B,OAAO,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IACrE,CAAC;CACF;AAED,MAAM,UAAU,cAAc,CAC5B,IAAgB,EAChB,SAA0B,EAC1B,IAEC;IAED,IAAI,CAAC,KAAK,CAAC;QACT,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;YAChD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAE1C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,IAAI,QAAQ,CAAC,4BAA4B,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YACrE,CAAC;YAED,MAAM,WAAW,GAAG,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;YAEnD,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,gBAAgB,EAAE;oBACrD,IAAI;oBACJ,WAAW;iBACZ,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;gBACzE,OAAO,IAAI,QAAQ,CAAC,0BAA0B,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YAC5E,CAAC;YAED,IAAI,IAAI,EAAE,SAAS,EAAE,CAAC;gBACpB,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;oBACxB,MAAM,EAAE,GAAG;oBACX,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE;iBACtC,CAAC,CAAC;YACL,CAAC;YAED,OAAO,IAAI,QAAQ,CAAC,mCAAmC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5E,CAAC,CAAC;KACH,CAAC,CAAC;AACL,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -57,22 +57,21 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
57
57
|
};
|
|
58
58
|
public: {
|
|
59
59
|
exchangeAuthCode: FunctionReference<"action", "internal", {
|
|
60
|
-
clientId: string;
|
|
61
|
-
clientSecret: string;
|
|
62
60
|
code: string;
|
|
63
61
|
redirectUri: string;
|
|
64
62
|
}, any, Name>;
|
|
63
|
+
fetchJobPosting: FunctionReference<"action", "internal", {
|
|
64
|
+
upworkId: string;
|
|
65
|
+
}, any, Name>;
|
|
65
66
|
getAuthStatus: FunctionReference<"query", "internal", {}, any, Name>;
|
|
67
|
+
getJobPosting: FunctionReference<"query", "internal", {
|
|
68
|
+
upworkId: string;
|
|
69
|
+
}, any, Name>;
|
|
66
70
|
listJobPostings: FunctionReference<"query", "internal", {
|
|
67
71
|
limit?: number;
|
|
68
72
|
}, any, Name>;
|
|
69
|
-
refreshAccessToken: FunctionReference<"action", "internal", {
|
|
70
|
-
clientId: string;
|
|
71
|
-
clientSecret: string;
|
|
72
|
-
}, any, Name>;
|
|
73
|
+
refreshAccessToken: FunctionReference<"action", "internal", {}, any, Name>;
|
|
73
74
|
searchJobPostings: FunctionReference<"action", "internal", {
|
|
74
|
-
clientId: string;
|
|
75
|
-
clientSecret: string;
|
|
76
75
|
searchQuery?: string;
|
|
77
76
|
sortField?: string;
|
|
78
77
|
}, any, Name>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../../src/component/_generated/component.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,YAAY,CAAC,IAAI,SAAS,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,IAC3E;IACE,OAAO,EAAE;QACP,SAAS,EAAE,iBAAiB,CAC1B,OAAO,EACP,UAAU,EACV,EAAE,EACF;YACE,aAAa,EAAE,MAAM,CAAC;YACtB,GAAG,EAAE,MAAM,CAAC;YACZ,WAAW,EAAE,MAAM,CAAC;YACpB,SAAS,EAAE,MAAM,CAAC;YAClB,YAAY,EAAE,MAAM,CAAC;YACrB,SAAS,EAAE,MAAM,CAAC;SACnB,GAAG,IAAI,EACR,IAAI,CACL,CAAC;QACF,WAAW,EAAE,iBAAiB,CAC5B,UAAU,EACV,UAAU,EACV;YACE,WAAW,EAAE,MAAM,CAAC;YACpB,SAAS,EAAE,MAAM,CAAC;YAClB,YAAY,EAAE,MAAM,CAAC;YACrB,SAAS,EAAE,MAAM,CAAC;SACnB,EACD,IAAI,EACJ,IAAI,CACL,CAAC;QACF,iBAAiB,EAAE,iBAAiB,CAClC,UAAU,EACV,UAAU,EACV;YACE,QAAQ,EAAE,KAAK,CAAC;gBACd,YAAY,CAAC,EAAE,MAAM,CAAC;gBACtB,cAAc,CAAC,EAAE,MAAM,CAAC;gBACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;gBAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;gBAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;gBAC1B,eAAe,EAAE,MAAM,CAAC;gBACxB,WAAW,EAAE,MAAM,CAAC;gBACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;gBAClB,eAAe,EAAE,MAAM,CAAC;gBACxB,iBAAiB,EAAE,MAAM,CAAC;gBAC1B,MAAM,EAAE,KAAK,CAAC;oBAAE,IAAI,EAAE,MAAM,CAAA;iBAAE,CAAC,CAAC;gBAChC,WAAW,CAAC,EAAE,MAAM,CAAC;gBACrB,KAAK,EAAE,MAAM,CAAC;gBACd,QAAQ,EAAE,MAAM,CAAC;aAClB,CAAC,CAAC;SACJ,EACD,IAAI,EACJ,IAAI,CACL,CAAC;KACH,CAAC;IACF,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CACjC,QAAQ,EACR,UAAU,EACV;
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../../src/component/_generated/component.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,22 +21,41 @@ type SearchResult = {
|
|
|
21
21
|
postings: JobPostingRow[];
|
|
22
22
|
hasNextPage: boolean;
|
|
23
23
|
};
|
|
24
|
-
export declare const refreshAccessToken: import("convex/server").RegisteredAction<"public", {
|
|
25
|
-
clientId: string;
|
|
26
|
-
clientSecret: string;
|
|
27
|
-
}, Promise<void>>;
|
|
24
|
+
export declare const refreshAccessToken: import("convex/server").RegisteredAction<"public", {}, Promise<void>>;
|
|
28
25
|
export declare const exchangeAuthCode: import("convex/server").RegisteredAction<"public", {
|
|
29
|
-
clientId: string;
|
|
30
|
-
clientSecret: string;
|
|
31
26
|
code: string;
|
|
32
27
|
redirectUri: string;
|
|
33
28
|
}, Promise<void>>;
|
|
34
29
|
export declare const searchJobPostings: import("convex/server").RegisteredAction<"public", {
|
|
35
30
|
searchQuery?: string | undefined;
|
|
36
31
|
sortField?: string | undefined;
|
|
37
|
-
clientId: string;
|
|
38
|
-
clientSecret: string;
|
|
39
32
|
}, Promise<SearchResult>>;
|
|
33
|
+
export declare const getJobPosting: import("convex/server").RegisteredQuery<"public", {
|
|
34
|
+
upworkId: string;
|
|
35
|
+
}, Promise<{
|
|
36
|
+
_id: import("convex/values").GenericId<"jobPostings">;
|
|
37
|
+
_creationTime: number;
|
|
38
|
+
category?: string | undefined;
|
|
39
|
+
subcategory?: string | undefined;
|
|
40
|
+
duration?: string | undefined;
|
|
41
|
+
budgetAmount?: string | undefined;
|
|
42
|
+
budgetCurrency?: string | undefined;
|
|
43
|
+
clientTotalHires?: number | undefined;
|
|
44
|
+
clientCompanyName?: string | undefined;
|
|
45
|
+
upworkId: string;
|
|
46
|
+
title: string;
|
|
47
|
+
description: string;
|
|
48
|
+
skills: {
|
|
49
|
+
name: string;
|
|
50
|
+
}[];
|
|
51
|
+
experienceLevel: string;
|
|
52
|
+
createdDateTime: string;
|
|
53
|
+
publishedDateTime: string;
|
|
54
|
+
cachedAt: number;
|
|
55
|
+
} | null>>;
|
|
56
|
+
export declare const fetchJobPosting: import("convex/server").RegisteredAction<"public", {
|
|
57
|
+
upworkId: string;
|
|
58
|
+
}, Promise<JobPostingRow | null>>;
|
|
40
59
|
export declare const listJobPostings: import("convex/server").RegisteredQuery<"public", {
|
|
41
60
|
limit?: number | undefined;
|
|
42
61
|
}, Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/component/public.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/component/public.ts"],"names":[],"mappings":"AAqEA,KAAK,aAAa,GAAG;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,kBAAkB,uEAwC7B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;iBA8C3B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;yBAqG5B,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;UAUxB,CAAC;AA0BH,eAAO,MAAM,eAAe;;iCAgF1B,CAAC;AAIH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;KAc1B,CAAC;AAEH,eAAO,MAAM,aAAa,0GAYxB,CAAC"}
|
package/dist/component/public.js
CHANGED
|
@@ -2,6 +2,14 @@ import { query, action } from "./_generated/server.js";
|
|
|
2
2
|
import { api } from "./_generated/api.js";
|
|
3
3
|
import { v } from "convex/values";
|
|
4
4
|
const BASE_URL = "https://upwork-mock-server.onrender.com";
|
|
5
|
+
function getCredentials() {
|
|
6
|
+
const clientId = process.env.UPWORK_CLIENT_ID;
|
|
7
|
+
const clientSecret = process.env.UPWORK_CLIENT_SECRET;
|
|
8
|
+
if (!clientId || !clientSecret) {
|
|
9
|
+
throw new Error("Missing UPWORK_CLIENT_ID or UPWORK_CLIENT_SECRET environment variables.");
|
|
10
|
+
}
|
|
11
|
+
return { clientId, clientSecret };
|
|
12
|
+
}
|
|
5
13
|
function buildJobPostingsQuery(opts) {
|
|
6
14
|
const filterArg = opts.searchQuery
|
|
7
15
|
? `marketPlaceJobFilter: $marketPlaceJobFilter,`
|
|
@@ -46,11 +54,9 @@ query ${variableDefs} {
|
|
|
46
54
|
return { query, variables };
|
|
47
55
|
}
|
|
48
56
|
export const refreshAccessToken = action({
|
|
49
|
-
args: {
|
|
50
|
-
|
|
51
|
-
clientSecret
|
|
52
|
-
},
|
|
53
|
-
handler: async (ctx, args) => {
|
|
57
|
+
args: {},
|
|
58
|
+
handler: async (ctx) => {
|
|
59
|
+
const { clientId, clientSecret } = getCredentials();
|
|
54
60
|
const tokens = await ctx.runQuery(api.private.getTokens, {});
|
|
55
61
|
if (!tokens) {
|
|
56
62
|
throw new Error("No stored tokens found. Complete OAuth authorization first.");
|
|
@@ -64,8 +70,8 @@ export const refreshAccessToken = action({
|
|
|
64
70
|
},
|
|
65
71
|
body: new URLSearchParams({
|
|
66
72
|
grant_type: "refresh_token",
|
|
67
|
-
client_id:
|
|
68
|
-
client_secret:
|
|
73
|
+
client_id: clientId,
|
|
74
|
+
client_secret: clientSecret,
|
|
69
75
|
refresh_token: tokens.refreshToken,
|
|
70
76
|
}).toString(),
|
|
71
77
|
});
|
|
@@ -85,23 +91,22 @@ export const refreshAccessToken = action({
|
|
|
85
91
|
});
|
|
86
92
|
export const exchangeAuthCode = action({
|
|
87
93
|
args: {
|
|
88
|
-
clientId: v.string(),
|
|
89
|
-
clientSecret: v.string(),
|
|
90
94
|
code: v.string(),
|
|
91
95
|
redirectUri: v.string(),
|
|
92
96
|
},
|
|
93
97
|
handler: async (ctx, args) => {
|
|
98
|
+
const { clientId, clientSecret } = getCredentials();
|
|
94
99
|
const tokenUrl = `${BASE_URL}/api/v3/oauth2/token`;
|
|
95
100
|
const body = new URLSearchParams({
|
|
96
101
|
grant_type: "authorization_code",
|
|
97
|
-
client_id:
|
|
98
|
-
client_secret:
|
|
102
|
+
client_id: clientId,
|
|
103
|
+
client_secret: clientSecret,
|
|
99
104
|
code: args.code,
|
|
100
105
|
redirect_uri: args.redirectUri,
|
|
101
106
|
}).toString();
|
|
102
107
|
console.log("[exchangeAuthCode] POST", tokenUrl);
|
|
103
108
|
console.log("[exchangeAuthCode] redirect_uri:", args.redirectUri);
|
|
104
|
-
console.log("[exchangeAuthCode] client_id:",
|
|
109
|
+
console.log("[exchangeAuthCode] client_id:", clientId);
|
|
105
110
|
console.log("[exchangeAuthCode] code:", args.code);
|
|
106
111
|
const response = await fetch(tokenUrl, {
|
|
107
112
|
method: "POST",
|
|
@@ -127,8 +132,6 @@ export const exchangeAuthCode = action({
|
|
|
127
132
|
});
|
|
128
133
|
export const searchJobPostings = action({
|
|
129
134
|
args: {
|
|
130
|
-
clientId: v.string(),
|
|
131
|
-
clientSecret: v.string(),
|
|
132
135
|
searchQuery: v.optional(v.string()),
|
|
133
136
|
sortField: v.optional(v.string()),
|
|
134
137
|
},
|
|
@@ -138,10 +141,7 @@ export const searchJobPostings = action({
|
|
|
138
141
|
throw new Error("Not connected to Upwork. Complete OAuth authorization first.");
|
|
139
142
|
}
|
|
140
143
|
if (tokens.expiresAt < Date.now()) {
|
|
141
|
-
await ctx.runAction(api.public.refreshAccessToken, {
|
|
142
|
-
clientId: args.clientId,
|
|
143
|
-
clientSecret: args.clientSecret,
|
|
144
|
-
});
|
|
144
|
+
await ctx.runAction(api.public.refreshAccessToken, {});
|
|
145
145
|
tokens = await ctx.runQuery(api.private.getTokens, {});
|
|
146
146
|
if (!tokens) {
|
|
147
147
|
throw new Error("Failed to refresh token.");
|
|
@@ -206,6 +206,97 @@ export const searchJobPostings = action({
|
|
|
206
206
|
};
|
|
207
207
|
},
|
|
208
208
|
});
|
|
209
|
+
export const getJobPosting = query({
|
|
210
|
+
args: { upworkId: v.string() },
|
|
211
|
+
handler: async (ctx, args) => {
|
|
212
|
+
return ((await ctx.db
|
|
213
|
+
.query("jobPostings")
|
|
214
|
+
.withIndex("byUpworkId", (q) => q.eq("upworkId", args.upworkId))
|
|
215
|
+
.first()) ?? null);
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
function buildJobPostingQuery(upworkId) {
|
|
219
|
+
const query = `
|
|
220
|
+
query($id: ID!) {
|
|
221
|
+
marketplaceJobPosting(id: $id) {
|
|
222
|
+
id
|
|
223
|
+
title
|
|
224
|
+
description
|
|
225
|
+
category { id name }
|
|
226
|
+
subcategory { id name }
|
|
227
|
+
skills { name }
|
|
228
|
+
experienceLevel
|
|
229
|
+
duration
|
|
230
|
+
budget { amount currency }
|
|
231
|
+
createdDateTime
|
|
232
|
+
publishedDateTime
|
|
233
|
+
client { totalHires companyName }
|
|
234
|
+
}
|
|
235
|
+
}`;
|
|
236
|
+
return { query, variables: { id: upworkId } };
|
|
237
|
+
}
|
|
238
|
+
export const fetchJobPosting = action({
|
|
239
|
+
args: {
|
|
240
|
+
upworkId: v.string(),
|
|
241
|
+
},
|
|
242
|
+
handler: async (ctx, args) => {
|
|
243
|
+
let tokens = await ctx.runQuery(api.private.getTokens, {});
|
|
244
|
+
if (!tokens) {
|
|
245
|
+
throw new Error("Not connected to Upwork. Complete OAuth authorization first.");
|
|
246
|
+
}
|
|
247
|
+
if (tokens.expiresAt < Date.now()) {
|
|
248
|
+
await ctx.runAction(api.public.refreshAccessToken, {});
|
|
249
|
+
tokens = await ctx.runQuery(api.private.getTokens, {});
|
|
250
|
+
if (!tokens) {
|
|
251
|
+
throw new Error("Failed to refresh token.");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const graphqlUrl = `${BASE_URL}/graphql`;
|
|
255
|
+
const { query, variables } = buildJobPostingQuery(args.upworkId);
|
|
256
|
+
const response = await fetch(graphqlUrl, {
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: {
|
|
259
|
+
"Content-Type": "application/json",
|
|
260
|
+
Authorization: `Bearer ${tokens.accessToken}`,
|
|
261
|
+
},
|
|
262
|
+
body: JSON.stringify({ query, variables }),
|
|
263
|
+
});
|
|
264
|
+
if (!response.ok) {
|
|
265
|
+
const text = await response.text();
|
|
266
|
+
throw new Error(`Upwork GraphQL request failed (${response.status}): ${text}`);
|
|
267
|
+
}
|
|
268
|
+
const result = await response.json();
|
|
269
|
+
if (result.errors?.length) {
|
|
270
|
+
throw new Error(`Upwork GraphQL errors: ${result.errors.map((e) => e.message).join(", ")}`);
|
|
271
|
+
}
|
|
272
|
+
const node = result.data?.marketplaceJobPosting;
|
|
273
|
+
if (!node)
|
|
274
|
+
return null;
|
|
275
|
+
const budget = node.budget;
|
|
276
|
+
const client = node.client;
|
|
277
|
+
const category = node.category;
|
|
278
|
+
const subcategory = node.subcategory;
|
|
279
|
+
const skills = node.skills ?? [];
|
|
280
|
+
const posting = {
|
|
281
|
+
upworkId: String(node.id),
|
|
282
|
+
title: String(node.title ?? ""),
|
|
283
|
+
description: String(node.description ?? ""),
|
|
284
|
+
category: category?.name ?? undefined,
|
|
285
|
+
subcategory: subcategory?.name ?? undefined,
|
|
286
|
+
skills: skills.map((s) => ({ name: s.name })),
|
|
287
|
+
experienceLevel: String(node.experienceLevel ?? ""),
|
|
288
|
+
duration: node.duration != null ? String(node.duration) : undefined,
|
|
289
|
+
budgetAmount: budget?.amount != null ? String(budget.amount) : undefined,
|
|
290
|
+
budgetCurrency: budget?.currency != null ? String(budget.currency) : undefined,
|
|
291
|
+
createdDateTime: String(node.createdDateTime ?? ""),
|
|
292
|
+
publishedDateTime: String(node.publishedDateTime ?? ""),
|
|
293
|
+
clientTotalHires: client?.totalHires ?? undefined,
|
|
294
|
+
clientCompanyName: client?.companyName ?? undefined,
|
|
295
|
+
};
|
|
296
|
+
await ctx.runMutation(api.private.upsertJobPostings, { postings: [posting] });
|
|
297
|
+
return posting;
|
|
298
|
+
},
|
|
299
|
+
});
|
|
209
300
|
const TWENTY_THREE_HOURS_MS = 23 * 60 * 60 * 1000;
|
|
210
301
|
export const listJobPostings = query({
|
|
211
302
|
args: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.js","sourceRoot":"","sources":["../../src/component/public.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"public.js","sourceRoot":"","sources":["../../src/component/public.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/client/index.ts
CHANGED
|
@@ -11,14 +11,19 @@ export type UpworkComponent = ComponentApi;
|
|
|
11
11
|
export class Upwork {
|
|
12
12
|
constructor(public component: UpworkComponent) {}
|
|
13
13
|
|
|
14
|
-
getAuthorizationUrl(
|
|
15
|
-
clientId
|
|
16
|
-
siteUrl
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
getAuthorizationUrl(): string {
|
|
15
|
+
const clientId = process.env.UPWORK_CLIENT_ID;
|
|
16
|
+
const siteUrl = process.env.CONVEX_SITE_URL;
|
|
17
|
+
if (!clientId) {
|
|
18
|
+
throw new Error("Missing UPWORK_CLIENT_ID environment variable.");
|
|
19
|
+
}
|
|
20
|
+
if (!siteUrl) {
|
|
21
|
+
throw new Error("Missing CONVEX_SITE_URL environment variable.");
|
|
22
|
+
}
|
|
23
|
+
const redirectUri = `${siteUrl}${CALLBACK_PATH}`;
|
|
19
24
|
const params = new URLSearchParams({
|
|
20
25
|
response_type: "code",
|
|
21
|
-
client_id:
|
|
26
|
+
client_id: clientId,
|
|
22
27
|
redirect_uri: redirectUri,
|
|
23
28
|
});
|
|
24
29
|
return `${BASE_URL}/ab/account-security/oauth2/authorize?${params.toString()}`;
|
|
@@ -26,17 +31,14 @@ export class Upwork {
|
|
|
26
31
|
|
|
27
32
|
async exchangeAuthCode(
|
|
28
33
|
ctx: ActionCtx,
|
|
29
|
-
opts: {
|
|
30
|
-
clientId: string;
|
|
31
|
-
clientSecret: string;
|
|
32
|
-
code: string;
|
|
33
|
-
siteUrl: string;
|
|
34
|
-
},
|
|
34
|
+
opts: { code: string },
|
|
35
35
|
): Promise<void> {
|
|
36
|
-
const
|
|
36
|
+
const siteUrl = process.env.CONVEX_SITE_URL;
|
|
37
|
+
if (!siteUrl) {
|
|
38
|
+
throw new Error("Missing CONVEX_SITE_URL environment variable.");
|
|
39
|
+
}
|
|
40
|
+
const redirectUri = `${siteUrl}${CALLBACK_PATH}`;
|
|
37
41
|
await ctx.runAction(this.component.public.exchangeAuthCode, {
|
|
38
|
-
clientId: opts.clientId,
|
|
39
|
-
clientSecret: opts.clientSecret,
|
|
40
42
|
code: opts.code,
|
|
41
43
|
redirectUri,
|
|
42
44
|
});
|
|
@@ -44,18 +46,32 @@ export class Upwork {
|
|
|
44
46
|
|
|
45
47
|
async searchJobPostings(
|
|
46
48
|
ctx: ActionCtx,
|
|
47
|
-
opts
|
|
48
|
-
clientId: string;
|
|
49
|
-
clientSecret: string;
|
|
49
|
+
opts?: {
|
|
50
50
|
searchQuery?: string;
|
|
51
51
|
sortField?: string;
|
|
52
52
|
},
|
|
53
53
|
): Promise<SearchResult> {
|
|
54
54
|
return await ctx.runAction(this.component.public.searchJobPostings, {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
searchQuery: opts?.searchQuery,
|
|
56
|
+
sortField: opts?.sortField,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async getJobPosting(
|
|
61
|
+
ctx: QueryCtx,
|
|
62
|
+
opts: { upworkId: string },
|
|
63
|
+
): Promise<JobPosting | null> {
|
|
64
|
+
return await ctx.runQuery(this.component.public.getJobPosting, {
|
|
65
|
+
upworkId: opts.upworkId,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async fetchJobPosting(
|
|
70
|
+
ctx: ActionCtx,
|
|
71
|
+
opts: { upworkId: string },
|
|
72
|
+
): Promise<JobPosting | null> {
|
|
73
|
+
return await ctx.runAction(this.component.public.fetchJobPosting, {
|
|
74
|
+
upworkId: opts.upworkId,
|
|
59
75
|
});
|
|
60
76
|
}
|
|
61
77
|
|
|
@@ -76,22 +92,10 @@ export class Upwork {
|
|
|
76
92
|
export function registerRoutes(
|
|
77
93
|
http: HttpRouter,
|
|
78
94
|
component: UpworkComponent,
|
|
79
|
-
opts
|
|
80
|
-
clientId: string;
|
|
81
|
-
clientSecret: string;
|
|
95
|
+
opts?: {
|
|
82
96
|
onSuccess?: string;
|
|
83
97
|
},
|
|
84
98
|
) {
|
|
85
|
-
const missing = [];
|
|
86
|
-
if (!opts.clientId) missing.push("clientId");
|
|
87
|
-
if (!opts.clientSecret) missing.push("clientSecret");
|
|
88
|
-
if (missing.length > 0) {
|
|
89
|
-
throw new Error(
|
|
90
|
-
`registerRoutes: missing required options: ${missing.join(", ")}. ` +
|
|
91
|
-
`Make sure UPWORK_CLIENT_ID and UPWORK_CLIENT_SECRET environment variables are set.`,
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
99
|
http.route({
|
|
96
100
|
path: CALLBACK_PATH,
|
|
97
101
|
method: "GET",
|
|
@@ -107,8 +111,6 @@ export function registerRoutes(
|
|
|
107
111
|
|
|
108
112
|
try {
|
|
109
113
|
await ctx.runAction(component.public.exchangeAuthCode, {
|
|
110
|
-
clientId: opts.clientId,
|
|
111
|
-
clientSecret: opts.clientSecret,
|
|
112
114
|
code,
|
|
113
115
|
redirectUri,
|
|
114
116
|
});
|
|
@@ -117,7 +119,7 @@ export function registerRoutes(
|
|
|
117
119
|
return new Response(`OAuth callback failed: ${message}`, { status: 500 });
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
if (opts
|
|
122
|
+
if (opts?.onSuccess) {
|
|
121
123
|
return new Response(null, {
|
|
122
124
|
status: 302,
|
|
123
125
|
headers: { Location: opts.onSuccess },
|
|
@@ -79,16 +79,25 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
79
79
|
exchangeAuthCode: FunctionReference<
|
|
80
80
|
"action",
|
|
81
81
|
"internal",
|
|
82
|
-
{
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
{ code: string; redirectUri: string },
|
|
83
|
+
any,
|
|
84
|
+
Name
|
|
85
|
+
>;
|
|
86
|
+
fetchJobPosting: FunctionReference<
|
|
87
|
+
"action",
|
|
88
|
+
"internal",
|
|
89
|
+
{ upworkId: string },
|
|
88
90
|
any,
|
|
89
91
|
Name
|
|
90
92
|
>;
|
|
91
93
|
getAuthStatus: FunctionReference<"query", "internal", {}, any, Name>;
|
|
94
|
+
getJobPosting: FunctionReference<
|
|
95
|
+
"query",
|
|
96
|
+
"internal",
|
|
97
|
+
{ upworkId: string },
|
|
98
|
+
any,
|
|
99
|
+
Name
|
|
100
|
+
>;
|
|
92
101
|
listJobPostings: FunctionReference<
|
|
93
102
|
"query",
|
|
94
103
|
"internal",
|
|
@@ -99,19 +108,14 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
99
108
|
refreshAccessToken: FunctionReference<
|
|
100
109
|
"action",
|
|
101
110
|
"internal",
|
|
102
|
-
{
|
|
111
|
+
{},
|
|
103
112
|
any,
|
|
104
113
|
Name
|
|
105
114
|
>;
|
|
106
115
|
searchJobPostings: FunctionReference<
|
|
107
116
|
"action",
|
|
108
117
|
"internal",
|
|
109
|
-
{
|
|
110
|
-
clientId: string;
|
|
111
|
-
clientSecret: string;
|
|
112
|
-
searchQuery?: string;
|
|
113
|
-
sortField?: string;
|
|
114
|
-
},
|
|
118
|
+
{ searchQuery?: string; sortField?: string },
|
|
115
119
|
any,
|
|
116
120
|
Name
|
|
117
121
|
>;
|
package/src/component/public.ts
CHANGED
|
@@ -4,6 +4,17 @@ import { v } from "convex/values";
|
|
|
4
4
|
|
|
5
5
|
const BASE_URL = "https://upwork-mock-server.onrender.com";
|
|
6
6
|
|
|
7
|
+
function getCredentials(): { clientId: string; clientSecret: string } {
|
|
8
|
+
const clientId = process.env.UPWORK_CLIENT_ID;
|
|
9
|
+
const clientSecret = process.env.UPWORK_CLIENT_SECRET;
|
|
10
|
+
if (!clientId || !clientSecret) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"Missing UPWORK_CLIENT_ID or UPWORK_CLIENT_SECRET environment variables.",
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
return { clientId, clientSecret };
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
function buildJobPostingsQuery(opts: {
|
|
8
19
|
searchQuery?: string;
|
|
9
20
|
sortField?: string;
|
|
@@ -80,11 +91,9 @@ type SearchResult = {
|
|
|
80
91
|
};
|
|
81
92
|
|
|
82
93
|
export const refreshAccessToken = action({
|
|
83
|
-
args: {
|
|
84
|
-
|
|
85
|
-
clientSecret
|
|
86
|
-
},
|
|
87
|
-
handler: async (ctx, args) => {
|
|
94
|
+
args: {},
|
|
95
|
+
handler: async (ctx) => {
|
|
96
|
+
const { clientId, clientSecret } = getCredentials();
|
|
88
97
|
const tokens = await ctx.runQuery(api.private.getTokens, {});
|
|
89
98
|
if (!tokens) {
|
|
90
99
|
throw new Error("No stored tokens found. Complete OAuth authorization first.");
|
|
@@ -100,8 +109,8 @@ export const refreshAccessToken = action({
|
|
|
100
109
|
},
|
|
101
110
|
body: new URLSearchParams({
|
|
102
111
|
grant_type: "refresh_token",
|
|
103
|
-
client_id:
|
|
104
|
-
client_secret:
|
|
112
|
+
client_id: clientId,
|
|
113
|
+
client_secret: clientSecret,
|
|
105
114
|
refresh_token: tokens.refreshToken,
|
|
106
115
|
}).toString(),
|
|
107
116
|
});
|
|
@@ -125,25 +134,24 @@ export const refreshAccessToken = action({
|
|
|
125
134
|
|
|
126
135
|
export const exchangeAuthCode = action({
|
|
127
136
|
args: {
|
|
128
|
-
clientId: v.string(),
|
|
129
|
-
clientSecret: v.string(),
|
|
130
137
|
code: v.string(),
|
|
131
138
|
redirectUri: v.string(),
|
|
132
139
|
},
|
|
133
140
|
handler: async (ctx, args): Promise<void> => {
|
|
141
|
+
const { clientId, clientSecret } = getCredentials();
|
|
134
142
|
const tokenUrl = `${BASE_URL}/api/v3/oauth2/token`;
|
|
135
143
|
|
|
136
144
|
const body = new URLSearchParams({
|
|
137
145
|
grant_type: "authorization_code",
|
|
138
|
-
client_id:
|
|
139
|
-
client_secret:
|
|
146
|
+
client_id: clientId,
|
|
147
|
+
client_secret: clientSecret,
|
|
140
148
|
code: args.code,
|
|
141
149
|
redirect_uri: args.redirectUri,
|
|
142
150
|
}).toString();
|
|
143
151
|
|
|
144
152
|
console.log("[exchangeAuthCode] POST", tokenUrl);
|
|
145
153
|
console.log("[exchangeAuthCode] redirect_uri:", args.redirectUri);
|
|
146
|
-
console.log("[exchangeAuthCode] client_id:",
|
|
154
|
+
console.log("[exchangeAuthCode] client_id:", clientId);
|
|
147
155
|
console.log("[exchangeAuthCode] code:", args.code);
|
|
148
156
|
|
|
149
157
|
const response = await fetch(tokenUrl, {
|
|
@@ -174,8 +182,6 @@ export const exchangeAuthCode = action({
|
|
|
174
182
|
|
|
175
183
|
export const searchJobPostings = action({
|
|
176
184
|
args: {
|
|
177
|
-
clientId: v.string(),
|
|
178
|
-
clientSecret: v.string(),
|
|
179
185
|
searchQuery: v.optional(v.string()),
|
|
180
186
|
sortField: v.optional(v.string()),
|
|
181
187
|
},
|
|
@@ -186,10 +192,7 @@ export const searchJobPostings = action({
|
|
|
186
192
|
}
|
|
187
193
|
|
|
188
194
|
if (tokens.expiresAt < Date.now()) {
|
|
189
|
-
await ctx.runAction(api.public.refreshAccessToken, {
|
|
190
|
-
clientId: args.clientId,
|
|
191
|
-
clientSecret: args.clientSecret,
|
|
192
|
-
});
|
|
195
|
+
await ctx.runAction(api.public.refreshAccessToken, {});
|
|
193
196
|
tokens = await ctx.runQuery(api.private.getTokens, {});
|
|
194
197
|
if (!tokens) {
|
|
195
198
|
throw new Error("Failed to refresh token.");
|
|
@@ -280,6 +283,124 @@ export const searchJobPostings = action({
|
|
|
280
283
|
},
|
|
281
284
|
});
|
|
282
285
|
|
|
286
|
+
export const getJobPosting = query({
|
|
287
|
+
args: { upworkId: v.string() },
|
|
288
|
+
handler: async (ctx, args) => {
|
|
289
|
+
return (
|
|
290
|
+
(await ctx.db
|
|
291
|
+
.query("jobPostings")
|
|
292
|
+
.withIndex("byUpworkId", (q) => q.eq("upworkId", args.upworkId))
|
|
293
|
+
.first()) ?? null
|
|
294
|
+
);
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
function buildJobPostingQuery(upworkId: string): {
|
|
299
|
+
query: string;
|
|
300
|
+
variables: Record<string, unknown>;
|
|
301
|
+
} {
|
|
302
|
+
const query = `
|
|
303
|
+
query($id: ID!) {
|
|
304
|
+
marketplaceJobPosting(id: $id) {
|
|
305
|
+
id
|
|
306
|
+
title
|
|
307
|
+
description
|
|
308
|
+
category { id name }
|
|
309
|
+
subcategory { id name }
|
|
310
|
+
skills { name }
|
|
311
|
+
experienceLevel
|
|
312
|
+
duration
|
|
313
|
+
budget { amount currency }
|
|
314
|
+
createdDateTime
|
|
315
|
+
publishedDateTime
|
|
316
|
+
client { totalHires companyName }
|
|
317
|
+
}
|
|
318
|
+
}`;
|
|
319
|
+
return { query, variables: { id: upworkId } };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export const fetchJobPosting = action({
|
|
323
|
+
args: {
|
|
324
|
+
upworkId: v.string(),
|
|
325
|
+
},
|
|
326
|
+
handler: async (ctx, args): Promise<JobPostingRow | null> => {
|
|
327
|
+
let tokens = await ctx.runQuery(api.private.getTokens, {});
|
|
328
|
+
if (!tokens) {
|
|
329
|
+
throw new Error("Not connected to Upwork. Complete OAuth authorization first.");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (tokens.expiresAt < Date.now()) {
|
|
333
|
+
await ctx.runAction(api.public.refreshAccessToken, {});
|
|
334
|
+
tokens = await ctx.runQuery(api.private.getTokens, {});
|
|
335
|
+
if (!tokens) {
|
|
336
|
+
throw new Error("Failed to refresh token.");
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const graphqlUrl = `${BASE_URL}/graphql`;
|
|
341
|
+
|
|
342
|
+
const { query, variables } = buildJobPostingQuery(args.upworkId);
|
|
343
|
+
|
|
344
|
+
const response = await fetch(graphqlUrl, {
|
|
345
|
+
method: "POST",
|
|
346
|
+
headers: {
|
|
347
|
+
"Content-Type": "application/json",
|
|
348
|
+
Authorization: `Bearer ${tokens.accessToken}`,
|
|
349
|
+
},
|
|
350
|
+
body: JSON.stringify({ query, variables }),
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
if (!response.ok) {
|
|
354
|
+
const text = await response.text();
|
|
355
|
+
throw new Error(`Upwork GraphQL request failed (${response.status}): ${text}`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const result: {
|
|
359
|
+
data?: { marketplaceJobPosting?: Record<string, unknown> };
|
|
360
|
+
errors?: Array<{ message: string }>;
|
|
361
|
+
} = await response.json();
|
|
362
|
+
|
|
363
|
+
if (result.errors?.length) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`Upwork GraphQL errors: ${result.errors.map((e) => e.message).join(", ")}`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const node = result.data?.marketplaceJobPosting;
|
|
370
|
+
if (!node) return null;
|
|
371
|
+
|
|
372
|
+
const budget = node.budget as { amount?: string; currency?: string } | null;
|
|
373
|
+
const client = node.client as {
|
|
374
|
+
totalHires?: number;
|
|
375
|
+
companyName?: string;
|
|
376
|
+
} | null;
|
|
377
|
+
const category = node.category as { name?: string } | null;
|
|
378
|
+
const subcategory = node.subcategory as { name?: string } | null;
|
|
379
|
+
const skills = (node.skills as Array<{ name: string }>) ?? [];
|
|
380
|
+
|
|
381
|
+
const posting: JobPostingRow = {
|
|
382
|
+
upworkId: String(node.id),
|
|
383
|
+
title: String(node.title ?? ""),
|
|
384
|
+
description: String(node.description ?? ""),
|
|
385
|
+
category: category?.name ?? undefined,
|
|
386
|
+
subcategory: subcategory?.name ?? undefined,
|
|
387
|
+
skills: skills.map((s) => ({ name: s.name })),
|
|
388
|
+
experienceLevel: String(node.experienceLevel ?? ""),
|
|
389
|
+
duration: node.duration != null ? String(node.duration) : undefined,
|
|
390
|
+
budgetAmount: budget?.amount != null ? String(budget.amount) : undefined,
|
|
391
|
+
budgetCurrency: budget?.currency != null ? String(budget.currency) : undefined,
|
|
392
|
+
createdDateTime: String(node.createdDateTime ?? ""),
|
|
393
|
+
publishedDateTime: String(node.publishedDateTime ?? ""),
|
|
394
|
+
clientTotalHires: client?.totalHires ?? undefined,
|
|
395
|
+
clientCompanyName: client?.companyName ?? undefined,
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
await ctx.runMutation(api.private.upsertJobPostings, { postings: [posting] });
|
|
399
|
+
|
|
400
|
+
return posting;
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
|
|
283
404
|
const TWENTY_THREE_HOURS_MS = 23 * 60 * 60 * 1000;
|
|
284
405
|
|
|
285
406
|
export const listJobPostings = query({
|