-
-
Notifications
You must be signed in to change notification settings - Fork 25
/
folksonomy.ts
148 lines (130 loc) · 3.82 KB
/
folksonomy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import createClient from "openapi-fetch";
import { paths, components } from "./schemas/folksonomy";
import { formBody as formBodySerializer } from "./formbody";
import { ApiError } from "./error";
export type FolksonomyTag = components["schemas"]["ProductTag"];
export type FolksonomyKey = {
k: string;
count: number;
values: number;
};
export class Folksonomy {
private readonly fetch: typeof global.fetch;
private readonly baseUrl: string;
readonly raw: ReturnType<typeof createClient<paths>>;
constructor(fetch: typeof global.fetch, authToken: string) {
this.baseUrl = "https://api.folksonomy.openfoodfacts.org";
this.fetch = fetch;
this.raw = createClient({
baseUrl: this.baseUrl,
fetch,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
},
});
}
/**
* Get the list of keys with statistics
*
* The keys list can be restricted to private tags from some owner.
*/
async getKeys(): Promise<FolksonomyKey[]> {
const res = await this.raw.GET("/keys");
return res.response.json();
}
/**
* Get the list of products that have a `key` or `key=value` if `value` is provided
*/
async getProducts(key: string, value?: string): Promise<FolksonomyTag[]> {
const res = await this.raw.GET("/products", {
params: { query: { k: key, v: value } },
});
return res.response.json();
}
async putTag(tag: FolksonomyTag): Promise<boolean> {
const res = await this.raw.PUT("/product", { body: tag });
return res.response.status === 200;
}
/**
* Get a list of existing tags for a product
*/
async getProduct(barcode: string) {
const res = await this.raw.GET("/product/{product}", {
params: { path: { product: barcode } },
});
return res;
}
/**
* Update a product tag (or add it if it does not exist)
*
* @param tag Tag to add or update with the following fields:
* - `k`: key
* - `v`: value
* - `product`: barcode
* - `version`: version of the tag (must be equal to previous version + 1)
* - `owner`: user_id of the owner of the tag (empty for public tags)
*
* @returns if the tag was added or updated
*/
async addTag(tag: FolksonomyTag): Promise<boolean> {
const res = await this.raw.POST("/product", {
body: tag,
});
return res.response.status === 200;
}
/**
* Delete a product tag
*
* @returns if the tag was deleted
*/
async removeTag(tag: FolksonomyTag & { version: number }) {
const res = await this.raw.DELETE("/product/{product}/{k}", {
params: {
path: { product: tag.product, k: tag.k },
query: { version: tag.version },
},
});
return res;
}
/**
* Authentication: provide user/password and get a bearer token in return
*
* @param username Open Food Facts user_id (not email)
* @param password user password
* @returns the bearer token, to be used in later requests with usual "Authorization: bearer token" headers
*/
async login(
username: string,
password: string
): Promise<
| { token: { access_token: string; token_type: string } }
| { error: ApiError }
> {
const res = await this.raw.POST("/auth", {
body: { username, password },
headers: { "Content-Type": "application/x-www-form-urlencoded" },
bodySerializer: formBodySerializer,
});
if (res.response.status !== 200) {
return {
error: {
detail: [
{
msg: "Status code " + res.response.status,
type: "error",
loc: [],
},
],
},
};
} else if (res.error != null) {
return { error: res.error };
}
const token = (await res.response.json()) as {
access_token: string;
token_type: string;
};
return { token };
}
}