mirror of
https://github.com/discourse/discourse.git
synced 2024-12-11 19:53:52 +08:00
7fee3c61de
* Support for custom messages and redirects when creating posts When a post/topic is created Discourse serializes a `NewPostResult` object. Normally this contains a status like `created_post` or errors describing why the post could not be created. There are times when a plugin might want to take the inputted post and do something in the background. In this case, the plugin can return a custom `message` and `route_to` attribute in the `NewPostResult`. If present, the message will be displayed in an alert, and when "Ok" is clicked the user will be routed to the new URL. * Destroy the draft in parallel
740 lines
20 KiB
JavaScript
740 lines
20 KiB
JavaScript
import User from "discourse/models/user";
|
|
|
|
export function parsePostData(query) {
|
|
const result = {};
|
|
query.split("&").forEach(function(part) {
|
|
const item = part.split("=");
|
|
const firstSeg = decodeURIComponent(item[0]);
|
|
const m = /^([^\[]+)\[(.+)\]/.exec(firstSeg);
|
|
|
|
const val = decodeURIComponent(item[1]).replace(/\+/g, " ");
|
|
if (m) {
|
|
let key = m[1];
|
|
result[key] = result[key] || {};
|
|
result[key][m[2].replace("][", ".")] = val;
|
|
} else {
|
|
result[firstSeg] = val;
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
export function response(code, obj) {
|
|
if (typeof code === "object") {
|
|
obj = code;
|
|
code = 200;
|
|
}
|
|
return [code, { "Content-Type": "application/json" }, obj];
|
|
}
|
|
|
|
export function success() {
|
|
return response({ success: true });
|
|
}
|
|
|
|
const loggedIn = () => !!User.current();
|
|
const helpers = { response, success, parsePostData };
|
|
export let fixturesByUrl;
|
|
|
|
export default function() {
|
|
const server = new Pretender(function() {
|
|
// Autoload any `*-pretender` files
|
|
Object.keys(requirejs.entries).forEach(e => {
|
|
let m = e.match(/^.*helpers\/([a-z-]+)\-pretender$/);
|
|
if (m && m[1] !== "create") {
|
|
let result = requirejs(e).default.call(this, helpers);
|
|
if (m[1] === "fixture") {
|
|
fixturesByUrl = result;
|
|
}
|
|
}
|
|
});
|
|
|
|
this.get("/admin/plugins", () => response({ plugins: [] }));
|
|
|
|
this.get("/composer_messages", () => response({ composer_messages: [] }));
|
|
|
|
this.get("/latest.json", () => {
|
|
const json = fixturesByUrl["/latest.json"];
|
|
|
|
if (loggedIn()) {
|
|
// Stuff to let us post
|
|
json.topic_list.can_create_topic = true;
|
|
json.topic_list.draft_key = "new_topic";
|
|
json.topic_list.draft_sequence = 1;
|
|
}
|
|
return response(json);
|
|
});
|
|
|
|
this.get("/c/bug/l/latest.json", () => {
|
|
const json = fixturesByUrl["/c/bug/l/latest.json"];
|
|
|
|
if (loggedIn()) {
|
|
// Stuff to let us post
|
|
json.topic_list.can_create_topic = true;
|
|
json.topic_list.draft_key = "new_topic";
|
|
json.topic_list.draft_sequence = 1;
|
|
}
|
|
return response(json);
|
|
});
|
|
|
|
this.get("/tags", () => {
|
|
return response({
|
|
tags: [
|
|
{
|
|
id: "eviltrout",
|
|
count: 1
|
|
}
|
|
]
|
|
});
|
|
});
|
|
|
|
this.get("/tags/filter/search", () => {
|
|
return response({ results: [{ text: "monkey", count: 1 }] });
|
|
});
|
|
|
|
this.get(`/u/:username/emails.json`, () => {
|
|
return response({ email: "eviltrout@example.com" });
|
|
});
|
|
|
|
this.get("/u/eviltrout.json", () => {
|
|
const json = fixturesByUrl["/u/eviltrout.json"];
|
|
json.user.can_edit = loggedIn();
|
|
return response(json);
|
|
});
|
|
|
|
this.get("/u/eviltrout/summary.json", () => {
|
|
return response({
|
|
user_summary: {
|
|
topic_ids: [1234],
|
|
replies: [{ topic_id: 1234 }],
|
|
links: [{ topic_id: 1234, url: "https://eviltrout.com" }],
|
|
most_replied_to_users: [{ id: 333 }],
|
|
most_liked_by_users: [{ id: 333 }],
|
|
most_liked_users: [{ id: 333 }],
|
|
badges: [{ badge_id: 444 }],
|
|
top_categories: [
|
|
{
|
|
id: 1,
|
|
name: "bug",
|
|
color: "e9dd00",
|
|
text_color: "000000",
|
|
slug: "bug",
|
|
read_restricted: false,
|
|
parent_category_id: null,
|
|
topic_count: 1,
|
|
post_count: 1
|
|
}
|
|
]
|
|
},
|
|
badges: [{ id: 444, count: 1 }],
|
|
topics: [{ id: 1234, title: "cool title", url: "/t/1234/cool-title" }]
|
|
});
|
|
});
|
|
|
|
this.get("/u/eviltrout/invited_count.json", () => {
|
|
return response({
|
|
counts: { pending: 1, redeemed: 0, total: 0 }
|
|
});
|
|
});
|
|
|
|
this.get("/u/eviltrout/invited.json", () => {
|
|
return response({ invites: [{ id: 1 }] });
|
|
});
|
|
|
|
this.get("/topics/private-messages/eviltrout.json", () => {
|
|
return response(fixturesByUrl["/topics/private-messages/eviltrout.json"]);
|
|
});
|
|
|
|
this.get("/topics/feature_stats.json", () => {
|
|
return response({
|
|
pinned_in_category_count: 0,
|
|
pinned_globally_count: 0,
|
|
banner_count: 0
|
|
});
|
|
});
|
|
|
|
this.put("/t/34/convert-topic/public", () => {
|
|
return response({});
|
|
});
|
|
|
|
this.put("/t/280/make-banner", () => {
|
|
return response({});
|
|
});
|
|
|
|
this.put("/t/internationalization-localization/280/status", () => {
|
|
return response({
|
|
success: "OK",
|
|
topic_status_update: null
|
|
});
|
|
});
|
|
|
|
this.post("/clicks/track", success);
|
|
|
|
this.get("/search", request => {
|
|
if (request.queryParams.q === "posts") {
|
|
return response({
|
|
posts: [
|
|
{
|
|
id: 1234
|
|
}
|
|
]
|
|
});
|
|
} else if (request.queryParams.q === "evil") {
|
|
return response({
|
|
posts: [
|
|
{
|
|
id: 1234
|
|
}
|
|
],
|
|
tags: [
|
|
{
|
|
id: 6,
|
|
name: "eviltrout"
|
|
}
|
|
]
|
|
});
|
|
}
|
|
|
|
return response({});
|
|
});
|
|
|
|
this.put("/u/eviltrout.json", () => response({ user: {} }));
|
|
|
|
this.get("/t/280.json", () => response(fixturesByUrl["/t/280/1.json"]));
|
|
this.get("/t/34.json", () => response(fixturesByUrl["/t/34/1.json"]));
|
|
this.get("/t/280/:post_number.json", () =>
|
|
response(fixturesByUrl["/t/280/1.json"])
|
|
);
|
|
this.get("/t/28830.json", () => response(fixturesByUrl["/t/28830/1.json"]));
|
|
this.get("/t/9.json", () => response(fixturesByUrl["/t/9/1.json"]));
|
|
this.get("/t/12.json", () => response(fixturesByUrl["/t/12/1.json"]));
|
|
this.put("/t/1234/re-pin", success);
|
|
|
|
this.get("/t/id_for/:slug", () => {
|
|
return response({
|
|
id: 280,
|
|
slug: "internationalization-localization",
|
|
url: "/t/internationalization-localization/280"
|
|
});
|
|
});
|
|
|
|
this.delete("/t/:id", success);
|
|
this.put("/t/:id/recover", success);
|
|
this.put("/t/:id/publish", success);
|
|
|
|
this.get("/404-body", () => {
|
|
return [
|
|
200,
|
|
{ "Content-Type": "text/html" },
|
|
"<div class='page-not-found'>not found</div>"
|
|
];
|
|
});
|
|
|
|
this.delete("/draft.json", success);
|
|
this.post("/draft.json", success);
|
|
|
|
this.get("/u/:username/staff-info.json", () => response({}));
|
|
|
|
this.get("/post_action_users", () => {
|
|
return response({
|
|
post_action_users: [
|
|
{
|
|
id: 1,
|
|
username: "eviltrout",
|
|
avatar_template: "/user_avatar/default/eviltrout/{size}/1.png",
|
|
username_lower: "eviltrout"
|
|
}
|
|
]
|
|
});
|
|
});
|
|
|
|
this.get("/post_replies", () => {
|
|
return response({ post_replies: [{ id: 1234, cooked: "wat" }] });
|
|
});
|
|
|
|
this.get("/post_reply_histories", () => {
|
|
return response({ post_reply_histories: [{ id: 1234, cooked: "wat" }] });
|
|
});
|
|
|
|
this.get("/category_hashtags/check", () => {
|
|
return response({ valid: [{ slug: "bug", url: "/c/bugs" }] });
|
|
});
|
|
|
|
this.get("/categories_and_latest", () =>
|
|
response(fixturesByUrl["/categories_and_latest.json"])
|
|
);
|
|
|
|
this.put("/categories/:category_id", request => {
|
|
const category = parsePostData(request.requestBody);
|
|
|
|
if (category.email_in === "duplicate@example.com") {
|
|
return response(422, { errors: ["duplicate email"] });
|
|
}
|
|
|
|
return response({ category });
|
|
});
|
|
|
|
this.get("/draft.json", request => {
|
|
if (request.queryParams.draft_key === "new_topic") {
|
|
return response(fixturesByUrl["/draft.json"]);
|
|
}
|
|
|
|
return response({});
|
|
});
|
|
|
|
this.get("/drafts.json", () => response(fixturesByUrl["/drafts.json"]));
|
|
|
|
this.put("/queued_posts/:queued_post_id", function(request) {
|
|
return response({ queued_post: { id: request.params.queued_post_id } });
|
|
});
|
|
|
|
this.get("/queued_posts", function() {
|
|
return response({
|
|
queued_posts: [
|
|
{ id: 1, raw: "queued post text", can_delete_user: true }
|
|
]
|
|
});
|
|
});
|
|
|
|
this.post("/session", function(request) {
|
|
const data = parsePostData(request.requestBody);
|
|
|
|
if (data.password === "correct") {
|
|
return response({ username: "eviltrout" });
|
|
}
|
|
|
|
if (data.password === "not-activated") {
|
|
return response({
|
|
error: "not active",
|
|
reason: "not_activated",
|
|
sent_to_email: "<small>eviltrout@example.com</small>",
|
|
current_email: "<small>current@example.com</small>"
|
|
});
|
|
}
|
|
|
|
if (data.password === "not-activated-edit") {
|
|
return response({
|
|
error: "not active",
|
|
reason: "not_activated",
|
|
sent_to_email: "eviltrout@example.com",
|
|
current_email: "current@example.com"
|
|
});
|
|
}
|
|
|
|
if (data.password === "need-second-factor") {
|
|
if (data.second_factor_token && data.second_factor_token === "123456") {
|
|
return response({ username: "eviltrout" });
|
|
}
|
|
|
|
return response({
|
|
error: "Invalid Second Factor",
|
|
reason: "invalid_second_factor",
|
|
backup_enabled: true,
|
|
sent_to_email: "eviltrout@example.com",
|
|
current_email: "current@example.com"
|
|
});
|
|
}
|
|
|
|
if (data.password === "need-security-key") {
|
|
if (data.securityKeyCredential) {
|
|
return response({ username: "eviltrout" });
|
|
}
|
|
|
|
return response({
|
|
error: "Invalid Security Key",
|
|
reason: "invalid_security_key",
|
|
backup_enabled: true,
|
|
sent_to_email: "eviltrout@example.com",
|
|
current_email: "current@example.com"
|
|
});
|
|
}
|
|
|
|
return response(400, { error: "invalid login" });
|
|
});
|
|
|
|
this.post("/u/action/send_activation_email", success);
|
|
this.put("/u/update-activation-email", success);
|
|
|
|
this.get("/u/hp.json", function() {
|
|
return response({
|
|
value: "32faff1b1ef1ac3",
|
|
challenge: "61a3de0ccf086fb9604b76e884d75801"
|
|
});
|
|
});
|
|
|
|
this.get("/session/csrf", function() {
|
|
return response({ csrf: "mgk906YLagHo2gOgM1ddYjAN4hQolBdJCqlY6jYzAYs=" });
|
|
});
|
|
|
|
this.get("/groups/check-name", () => {
|
|
return response({ available: true });
|
|
});
|
|
|
|
this.get("/u/check_username", function(request) {
|
|
if (request.queryParams.username === "taken") {
|
|
return response({ available: false, suggestion: "nottaken" });
|
|
}
|
|
return response({ available: true });
|
|
});
|
|
|
|
this.post("/u", () => response({ success: true }));
|
|
|
|
this.get("/login.html", () => [200, {}, "LOGIN PAGE"]);
|
|
|
|
this.delete("/posts/:post_id", success);
|
|
this.put("/posts/:post_id/recover", success);
|
|
this.get("/posts/:post_id/expand-embed", success);
|
|
|
|
this.put("/posts/:post_id", request => {
|
|
const data = parsePostData(request.requestBody);
|
|
data.post.id = request.params.post_id;
|
|
data.post.version = 2;
|
|
return response(200, data.post);
|
|
});
|
|
|
|
this.get("/t/403.json", () => response(403, {}));
|
|
this.get("/t/404.json", () => response(404, "not found"));
|
|
this.get("/t/500.json", () => response(502, {}));
|
|
|
|
this.put("/t/:slug/:id", request => {
|
|
const data = parsePostData(request.requestBody);
|
|
|
|
return response(200, {
|
|
basic_topic: {
|
|
id: request.params.id,
|
|
title: data.title,
|
|
fancy_title: data.title,
|
|
slug: request.params.slug
|
|
}
|
|
});
|
|
});
|
|
|
|
this.get("groups", () => {
|
|
return response(200, fixturesByUrl["/groups.json"]);
|
|
});
|
|
|
|
this.get("/groups.json", () => {
|
|
return response(200, fixturesByUrl["/groups.json?username=eviltrout"]);
|
|
});
|
|
|
|
this.get("groups/search.json", () => {
|
|
return response(200, []);
|
|
});
|
|
|
|
this.get("/topics/groups/discourse.json", () => {
|
|
return response(200, fixturesByUrl["/topics/groups/discourse.json"]);
|
|
});
|
|
|
|
this.get("/groups/discourse/mentions.json", () => {
|
|
return response(200, fixturesByUrl["/groups/discourse/posts.json"]);
|
|
});
|
|
|
|
this.get("/groups/discourse/messages.json", () => {
|
|
return response(200, fixturesByUrl["/groups/discourse/posts.json"]);
|
|
});
|
|
|
|
this.get("/groups/moderators/members.json", () => {
|
|
return response(200, fixturesByUrl["/groups/discourse/members.json"]);
|
|
});
|
|
|
|
this.get("/t/:topic_id/posts.json", request => {
|
|
const postIds = request.queryParams.post_ids;
|
|
const postNumber = parseInt(request.queryParams.post_number, 10);
|
|
let posts;
|
|
|
|
if (postIds) {
|
|
posts = postIds.map(p => ({
|
|
id: parseInt(p, 10),
|
|
post_number: parseInt(p, 10)
|
|
}));
|
|
} else if (postNumber && request.queryParams.asc === "true") {
|
|
posts = _.range(postNumber + 1, postNumber + 6).map(p => ({
|
|
id: parseInt(p, 10),
|
|
post_number: parseInt(p, 10)
|
|
}));
|
|
} else if (postNumber && request.queryParams.asc === "false") {
|
|
posts = _.range(postNumber - 5, postNumber)
|
|
.reverse()
|
|
.map(p => ({
|
|
id: parseInt(p, 10),
|
|
post_number: parseInt(p, 10)
|
|
}));
|
|
}
|
|
|
|
return response(200, { post_stream: { posts } });
|
|
});
|
|
|
|
this.get("/posts/:post_id/reply-history.json", () => {
|
|
return response(200, [{ id: 2222, post_number: 2222 }]);
|
|
});
|
|
|
|
this.get("/posts/:post_id/reply-ids.json", () => {
|
|
return response(200, {
|
|
direct_reply_ids: [45],
|
|
all_reply_ids: [45, 100]
|
|
});
|
|
});
|
|
|
|
this.post("/user_badges", () =>
|
|
response(200, fixturesByUrl["/user_badges"])
|
|
);
|
|
this.delete("/user_badges/:badge_id", success);
|
|
|
|
this.post("/posts", function(request) {
|
|
const data = parsePostData(request.requestBody);
|
|
|
|
if (data.title === "this title triggers an error") {
|
|
return response(422, { errors: ["That title has already been taken"] });
|
|
}
|
|
|
|
if (data.raw === "enqueue this content please") {
|
|
return response(200, {
|
|
success: true,
|
|
action: "enqueued",
|
|
pending_post: {
|
|
id: 1234,
|
|
raw: data.raw
|
|
}
|
|
});
|
|
}
|
|
|
|
if (data.raw === "custom message") {
|
|
return response(200, {
|
|
success: true,
|
|
action: "custom",
|
|
message: "This is a custom response",
|
|
route_to: "/faq"
|
|
});
|
|
}
|
|
|
|
return response(200, {
|
|
success: true,
|
|
action: "create_post",
|
|
post: {
|
|
id: 12345,
|
|
topic_id: 280,
|
|
topic_slug: "internationalization-localization"
|
|
}
|
|
});
|
|
});
|
|
|
|
this.post("/topics/timings", () => response(200, {}));
|
|
|
|
const siteText = { id: "site.test", value: "Test McTest" };
|
|
const overridden = {
|
|
id: "site.overridden",
|
|
value: "Overridden",
|
|
overridden: true
|
|
};
|
|
|
|
this.get("/admin/users/list/active.json", request => {
|
|
let store = [
|
|
{
|
|
id: 1,
|
|
username: "eviltrout",
|
|
email: "<small>eviltrout@example.com</small>"
|
|
},
|
|
{
|
|
id: 3,
|
|
username: "discobot",
|
|
email: "<small>discobot_email</small>"
|
|
}
|
|
];
|
|
|
|
const showEmails = request.queryParams.show_emails;
|
|
|
|
if (showEmails === "false") {
|
|
store = store.map(item => {
|
|
delete item.email;
|
|
return item;
|
|
});
|
|
}
|
|
|
|
const ascending = request.queryParams.ascending;
|
|
const order = request.queryParams.order;
|
|
|
|
if (order) {
|
|
store = store.sort(function(a, b) {
|
|
return a[order] - b[order];
|
|
});
|
|
}
|
|
|
|
if (ascending) {
|
|
store = store.reverse();
|
|
}
|
|
|
|
return response(200, store);
|
|
});
|
|
|
|
this.get("/admin/users/list/suspect.json", () => {
|
|
return response(200, [
|
|
{
|
|
id: 2,
|
|
username: "sam",
|
|
email: "<small>sam@example.com</small>"
|
|
}
|
|
]);
|
|
});
|
|
|
|
this.get("/admin/customize/site_texts", request => {
|
|
if (request.queryParams.overridden) {
|
|
return response(200, { site_texts: [overridden] });
|
|
} else {
|
|
return response(200, { site_texts: [siteText, overridden] });
|
|
}
|
|
});
|
|
|
|
this.get("/admin/customize/site_texts/:key", () =>
|
|
response(200, { site_text: siteText })
|
|
);
|
|
this.delete("/admin/customize/site_texts/:key", () =>
|
|
response(200, { site_text: siteText })
|
|
);
|
|
|
|
this.put("/admin/customize/site_texts/:key", request => {
|
|
const result = parsePostData(request.requestBody);
|
|
result.id = request.params.key;
|
|
result.can_revert = true;
|
|
return response(200, { site_text: result });
|
|
});
|
|
|
|
this.get("/tag_groups", () => response(200, { tag_groups: [] }));
|
|
|
|
this.get("/admin/users/1234.json", () => {
|
|
return response(200, {
|
|
id: 1234,
|
|
username: "regular"
|
|
});
|
|
});
|
|
|
|
this.get("/admin/users/1.json", () => {
|
|
return response(200, {
|
|
id: 1,
|
|
username: "eviltrout",
|
|
admin: true
|
|
});
|
|
});
|
|
|
|
this.get("/admin/users/2.json", () => {
|
|
return response(200, {
|
|
id: 2,
|
|
username: "sam",
|
|
admin: true
|
|
});
|
|
});
|
|
|
|
this.delete("/admin/users/:user_id.json", () =>
|
|
response(200, { deleted: true })
|
|
);
|
|
this.post("/admin/badges", success);
|
|
this.delete("/admin/badges/:id", success);
|
|
|
|
this.get("/admin/logs/staff_action_logs.json", () => {
|
|
return response(200, {
|
|
staff_action_logs: [],
|
|
extras: { user_history_actions: [] }
|
|
});
|
|
});
|
|
|
|
this.get("/admin/logs/watched_words", () => {
|
|
return response(200, fixturesByUrl["/admin/logs/watched_words.json"]);
|
|
});
|
|
this.delete("/admin/logs/watched_words/:id.json", success);
|
|
|
|
this.post("/admin/logs/watched_words.json", request => {
|
|
const result = parsePostData(request.requestBody);
|
|
result.id = new Date().getTime();
|
|
return response(200, result);
|
|
});
|
|
|
|
this.get("/admin/logs/search_logs.json", () => {
|
|
return response(200, [
|
|
{ term: "foobar", searches: 35, click_through: 6, unique: 16 }
|
|
]);
|
|
});
|
|
|
|
this.get("/admin/logs/search_logs/term.json", () => {
|
|
return response(200, {
|
|
term: {
|
|
type: "search_log_term",
|
|
title: "Search Count",
|
|
term: "ruby",
|
|
data: [{ x: "2017-07-20", y: 2 }]
|
|
}
|
|
});
|
|
});
|
|
|
|
this.post("/uploads/lookup-metadata", () => {
|
|
return response(200, {
|
|
imageFilename: "somefile.png",
|
|
imageFilesize: "10 KB",
|
|
imageWidth: "1",
|
|
imageHeight: "1"
|
|
});
|
|
});
|
|
|
|
this.get("/inline-onebox", request => {
|
|
if (
|
|
request.queryParams.urls.includes(
|
|
"http://www.example.com/has-title.html"
|
|
)
|
|
) {
|
|
return [
|
|
200,
|
|
{ "Content-Type": "application/html" },
|
|
'{"inline-oneboxes":[{"url":"http://www.example.com/has-title.html","title":"This is a great title"}]}'
|
|
];
|
|
}
|
|
});
|
|
|
|
this.get("/onebox", request => {
|
|
if (
|
|
request.queryParams.url === "http://www.example.com/has-title.html" ||
|
|
request.queryParams.url ===
|
|
"http://www.example.com/has-title-and-a-url-that-is-more-than-80-characters-because-thats-good-for-seo-i-guess.html"
|
|
) {
|
|
return [
|
|
200,
|
|
{ "Content-Type": "application/html" },
|
|
'<aside class="onebox"><article class="onebox-body"><h3><a href="http://www.example.com/article.html">An interesting article</a></h3></article></aside>'
|
|
];
|
|
}
|
|
|
|
if (request.queryParams.url === "http://www.example.com/no-title.html") {
|
|
return [
|
|
200,
|
|
{ "Content-Type": "application/html" },
|
|
'<aside class="onebox"><article class="onebox-body"><p>No title</p></article></aside>'
|
|
];
|
|
}
|
|
|
|
if (request.queryParams.url.indexOf("/internal-page.html") > -1) {
|
|
return [
|
|
200,
|
|
{ "Content-Type": "application/html" },
|
|
'<aside class="onebox"><article class="onebox-body"><h3><a href="/internal-page.html">Internal Page 4 U</a></h3></article></aside>'
|
|
];
|
|
}
|
|
|
|
return [404, { "Content-Type": "application/html" }, ""];
|
|
});
|
|
});
|
|
|
|
server.prepareBody = function(body) {
|
|
if (body && typeof body === "object") {
|
|
return JSON.stringify(body);
|
|
}
|
|
return body;
|
|
};
|
|
|
|
server.unhandledRequest = function(verb, path) {
|
|
const error =
|
|
"Unhandled request in test environment: " + path + " (" + verb + ")";
|
|
window.console.error(error);
|
|
throw error;
|
|
};
|
|
|
|
server.checkPassthrough = request =>
|
|
request.requestHeaders["Discourse-Script"];
|
|
return server;
|
|
}
|