feat(planning): grille hebdomadaire complète avec API et filtres

- Connexion API via proxy Angular (résolution CORS, base path /api)
- Import CSS ng-zorro global pour les modales et composants
- Filtres Camion/Show câblés sur l'affichage de la grille
- Camions affichés via TrucksService (linkés au show du même créneau)
- Panneau de détails : spectacles + camions du jour sélectionné
- Modale de création de spectacle stylisée avec fond et centrage
- Positionnement précis des events à la minute dans leur créneau
- Auto-scroll vers l'heure courante au chargement
- Ligne "maintenant" sur la colonne du jour actuel
- Régénération des services OpenAPI (nouveaux noms de types)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:36:03 +02:00
parent 150b97cd2e
commit 654b297e2e
3131 changed files with 149304 additions and 104334 deletions
+2 -2
View File
@@ -62,7 +62,7 @@ With only two real dependencies, Needle supports:
And yes, Mr. Wayne, it does come in black.
This makes Needle an ideal alternative for performing quick HTTP requests in Node, either for API interaction, downloading or uploading streams of data, and so on.
This makes Needle an ideal alternative for performing quick HTTP requests in Node, either for API interaction, downloading or uploading streams of data, and so on. If you need OAuth, AWS support or anything fancier, you should check out mikeal's request module.
Install
-------
@@ -362,7 +362,7 @@ Redirect options
These options only apply if the `follow_max` (or `follow`) option is higher than 0.
- `follow_set_cookies` : Sends the cookies received in the `set-cookie` header as part of the following request, *if hosts match*, along with the original request cookies (if provided). `false` by default.
- `follow_set_cookies` : Sends the cookies received in the `set-cookie` header as part of the following request, *if hosts match*. `false` by default.
- `follow_set_referer` : Sets the 'Referer' header to the requested URI when following a redirect. `false` by default.
- `follow_keep_method` : If enabled, resends the request using the original verb instead of being rewritten to `get` with no data. `false` by default.
- `follow_if_same_host` : When true, Needle will only follow redirects that point to the same host as the original request. `false` by default.
+1 -2
View File
@@ -20,7 +20,7 @@ var SEP_SEMICOLON = /\s*\x3B\s*/;
var KEY_INDEX = 1; // index of key from COOKIE_PAIR match
var VALUE_INDEX = 3; // index of value from COOKIE_PAIR match
// Returns a copy str trimmed and without trailing semicolon.
// Returns a copy str trimmed and without trainling semicolon.
function cleanCookieString(str) {
return str.trim().replace(/\x3B+$/, '');
}
@@ -53,7 +53,6 @@ function parseSetCookieString(str) {
// Each key represents the name of a cookie.
function parseSetCookieHeader(header) {
if (!header) return {};
if (typeof header === 'string' || header instanceof String) header = header.split(';');
header = Array.isArray(header) ? header : [header];
return header.reduce(function(res, str) {
+5 -6
View File
@@ -39,9 +39,10 @@ function generate_part(name, part, boundary, callback) {
var return_part = '--' + boundary + '\r\n';
return_part += 'Content-Disposition: form-data; name="' + name + '"';
function append(data, filename, force_binary) {
function append(data, filename) {
if (data) {
var binary = force_binary || part.content_type.indexOf('text') == -1;
var binary = part.content_type.indexOf('text') == -1;
return_part += '; filename="' + encodeURIComponent(filename) + '"\r\n';
if (binary) return_part += 'Content-Transfer-Encoding: binary\r\n';
return_part += 'Content-Type: ' + part.content_type + '\r\n\r\n';
@@ -54,16 +55,14 @@ function generate_part(name, part, boundary, callback) {
if ((part.file || part.buffer) && part.content_type) {
var filename = part.filename ? part.filename : part.file ? basename(part.file) : name;
if (part.buffer) return append(part.buffer, filename, true);
if (part.buffer) return append(part.buffer, filename);
readFile(part.file, function(err, data) {
if (err) return callback(err);
append(data, filename, true);
append(data, filename);
});
} else {
if (!part.value)
throw new Error('value missing for multipart!')
if (typeof part.value == 'object')
return callback(new Error('Object received for ' + name + ', expected string.'))
+16 -33
View File
@@ -455,8 +455,13 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data,
protocol = request_opts.protocol == 'https:' ? https : http,
signal = request_opts.signal;
function unlisten_errors() {
function done(err, resp) {
if (returned++ > 0)
return debug('Already finished, stopping here.');
if (timer) clearTimeout(timer);
request.removeListener('error', had_error);
out.done = true;
// An error can still be fired after closing. In particular, on macOS.
// See also:
@@ -464,16 +469,6 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data,
// - https://github.com/less/less.js/issues/3693
// - https://github.com/nodejs/node/issues/27916
request.once('error', function() {});
}
function done(err, resp) {
if (returned++ > 0)
return debug('Already finished, stopping here.');
if (timer) clearTimeout(timer);
out.done = true;
unlisten_errors();
if (callback)
return callback(err, resp, resp ? resp.body : undefined);
@@ -544,28 +539,17 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data,
delete config.headers['content-length']; // in case the original was a multipart POST request.
}
if (utils.host_and_ports_match(headers.location, uri)) {
// if follow_set_cookies is true, insert cookies in the next request's headers.
// we set both the original request cookies plus any response cookies we might have received.
if (config.follow_set_cookies) {
var request_cookies = cookies.read(config.headers['cookie']);
config.previous_resp_cookies = resp.cookies;
if (Object.keys(request_cookies).length || Object.keys(resp.cookies || {}).length) {
config.headers['cookie'] = cookies.write(extend(request_cookies, resp.cookies));
}
} else {
// set response cookies if present, otherwise remove header
// if (resp.cookies && Object.keys(resp.cookies).length)
// config.headers['cookie'] = cookies.write(resp.cookies);
// else
delete config.headers['cookie'];
// if follow_set_cookies is true, insert cookies in the next request's headers.
// we set both the original request cookies plus any response cookies we might have received.
if (config.follow_set_cookies && utils.host_and_ports_match(headers.location, uri)) {
var request_cookies = cookies.read(config.headers['cookie']);
config.previous_resp_cookies = resp.cookies;
if (Object.keys(request_cookies).length || Object.keys(resp.cookies || {}).length) {
config.headers['cookie'] = cookies.write(extend(request_cookies, resp.cookies));
}
} else {
} else if (config.headers['cookie']) {
debug('Clearing original request cookie', config.headers['cookie']);
delete config.headers['cookie'];
delete config.headers['authorization'];
delete config.headers['proxy-authorization'];
}
if (config.follow_set_referer)
@@ -575,7 +559,6 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data,
var redirect_url = utils.resolve_url(headers.location, uri);
debug('Redirecting to ' + redirect_url.toString());
unlisten_errors();
return self.send_request(++count, method, redirect_url.toString(), config, post_data, out, callback);
} else if (config.follow_max > 0) {
return done(new Error('Max redirects reached. Possible loop in: ' + headers.location));
@@ -860,7 +843,7 @@ module.exports.defaults = function(obj) {
'head get'.split(' ').forEach(function(method) {
module.exports[method] = function(uri, options, callback) {
return new Needle(method, uri, options.query, options, callback).start();
return new Needle(method, uri, null, options, callback).start();
}
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "needle",
"version": "3.5.0",
"version": "3.3.1",
"description": "The leanest and most handsome HTTP client in the Nodelands.",
"keywords": [
"http",
+6 -41
View File
@@ -265,41 +265,6 @@ describe('cookies', function() {
})
describe('with multiple original request cookies', function() {
var opts = {
follow_set_cookies: true,
follow_max: 4,
cookies: { 'xxx': 123, 'yyy': 456 }
};
it('request cookie is passed passed to redirects, and response cookies are added too', function(done) {
needle.get('localhost:' + testPort + '/0', opts, function(err, resp) {
requestCookies.should.eql([
"xxx=123; yyy=456",
"xxx=123; yyy=456; wc=!'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~; bc=Y29va2llCg==; FOO=123",
"xxx=123; yyy=456; wc=!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~; bc=Y29va2llCg==; FOO=123; fc=%20%3B%22%5C%2C; nc=12354342",
"xxx=123; yyy=456; wc=!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~; bc=Y29va2llCg==; FOO=BAR; fc=%20%3B%22%5C%2C; nc=12354342",
"xxx=123; yyy=456; wc=!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~; bc=Y29va2llCg==; FOO=BAR; fc=%20%3B%22%5C%2C; nc=12354342"
])
done();
});
});
it('response cookies are passed as well', function(done) {
needle.get('localhost:' + testPort + '/0', opts, function(err, resp) {
resp.cookies.should.have.property(WEIRD_COOKIE_NAME);
resp.cookies.should.have.property(BASE64_COOKIE_NAME);
resp.cookies.should.have.property(FORBIDDEN_COOKIE_NAME);
resp.cookies.should.have.property(NUMBER_COOKIE_NAME);
resp.cookies.should.have.property('FOO');
resp.cookies.FOO.should.eql('BAR'); // should overwrite previous one
done();
});
});
})
describe('without original request cookie', function() {
var opts = {
@@ -322,12 +287,12 @@ describe('cookies', function() {
it('response cookies are passed as well', function(done) {
needle.get('localhost:' + testPort + '/0', opts, function(err, resp) {
resp.cookies.should.have.property(WEIRD_COOKIE_NAME);
resp.cookies.should.have.property(BASE64_COOKIE_NAME);
resp.cookies.should.have.property(FORBIDDEN_COOKIE_NAME);
resp.cookies.should.have.property(NUMBER_COOKIE_NAME);
resp.cookies.should.have.property('FOO');
resp.cookies.FOO.should.eql('BAR'); // should overwrite previous one
// resp.cookies.should.have.property(WEIRD_COOKIE_NAME);
// resp.cookies.should.have.property(BASE64_COOKIE_NAME);
// resp.cookies.should.have.property(FORBIDDEN_COOKIE_NAME);
// resp.cookies.should.have.property(NUMBER_COOKIE_NAME);
// resp.cookies.should.have.property('FOO');
// resp.cookies.FOO.should.eql('BAR'); // should overwrite previous one
done();
});
});
+1 -28
View File
@@ -146,7 +146,7 @@ describe('post data (e.g. request body)', function() {
it('writes japanese chars correctly as binary', function(done) {
spystub_request();
post({ foo: 'bar', test: '测试' }, { multipart: true }, function(err, resp) {
get({ foo: 'bar', test: '测试' }, { multipart: true }, function(err, resp) {
spy.called.should.be.true;
spy.args[0][0].should.be.an.instanceof(String);
@@ -155,33 +155,6 @@ describe('post data (e.g. request body)', function() {
})
})
describe('when buffer', function() {
it('includes Content-Transfer-Encoding: binary header', function(done) {
spystub_request();
post({ foo: { buffer: Buffer.from('test content'), content_type: 'application/octet-stream' } }, { multipart: true }, function(err, resp) {
spy.called.should.be.true;
resp.body.body.should.containEql('Content-Transfer-Encoding: binary');
done();
})
})
})
describe('when file', function() {
it('includes Content-Transfer-Encoding: binary header', function(done) {
spystub_request();
post({ foo: { file: __dirname + '/keys/ssl.key', content_type: 'application/octet-stream' } }, { multipart: true }, function(err, resp) {
spy.called.should.be.true;
resp.body.body.should.containEql('Content-Transfer-Encoding: binary');
done();
})
})
})
})