allow changing of the image API server URL
This commit is contained in:
@@ -854,19 +854,19 @@ export default class ImageMethodDialog extends Vue {
|
||||
formData.append("image", this.blob, this.fileName || "photo.jpg");
|
||||
formData.append("claimType", this.claimType);
|
||||
try {
|
||||
const imageServer =
|
||||
(await this.$accountSettings()).imageServer || DEFAULT_IMAGE_API_SERVER;
|
||||
if (
|
||||
window.location.hostname === "localhost" &&
|
||||
!DEFAULT_IMAGE_API_SERVER.includes("localhost")
|
||||
!imageServer.includes("localhost")
|
||||
) {
|
||||
logger.log(
|
||||
"Using shared image API server, so only users on that server can play with images.",
|
||||
);
|
||||
}
|
||||
const response = await axios.post(
|
||||
DEFAULT_IMAGE_API_SERVER + "/image",
|
||||
formData,
|
||||
{ headers },
|
||||
);
|
||||
const response = await axios.post(imageServer + "/image", formData, {
|
||||
headers,
|
||||
});
|
||||
this.uploading = false;
|
||||
|
||||
this.close();
|
||||
|
||||
@@ -556,19 +556,19 @@ export default class PhotoDialog extends Vue {
|
||||
formData.append("image", this.blob, this.fileName || "photo.jpg");
|
||||
formData.append("claimType", this.claimType);
|
||||
try {
|
||||
const imageServer =
|
||||
(await this.$accountSettings()).imageServer || DEFAULT_IMAGE_API_SERVER;
|
||||
if (
|
||||
window.location.hostname === "localhost" &&
|
||||
!DEFAULT_IMAGE_API_SERVER.includes("localhost")
|
||||
!imageServer.includes("localhost")
|
||||
) {
|
||||
logger.log(
|
||||
"Using shared image API server, so only users on that server can play with images.",
|
||||
);
|
||||
}
|
||||
const response = await axios.post(
|
||||
DEFAULT_IMAGE_API_SERVER + "/image",
|
||||
formData,
|
||||
{ headers },
|
||||
);
|
||||
const response = await axios.post(imageServer + "/image", formData, {
|
||||
headers,
|
||||
});
|
||||
this.uploading = false;
|
||||
|
||||
this.close();
|
||||
|
||||
@@ -1401,6 +1401,22 @@ export const NOTIFY_PROJECT_DELETE_IMAGE_GENERAL_ERROR = {
|
||||
message: "There was an error deleting the image.",
|
||||
};
|
||||
|
||||
// Used in: AccountViewView.vue, GiftedDetailsView.vue, NewEditProjectView.vue
|
||||
// (deleteImage - the image server returned 404). The server looks an image up
|
||||
// by URL and uploader, so this also happens when the image was uploaded to a
|
||||
// different image server than the one in settings.
|
||||
export const NOTIFY_IMAGE_DELETE_NOT_FOUND = {
|
||||
title: "Image Not Deleted",
|
||||
message:
|
||||
"The image server in your settings has no record of that image, so it was not removed from storage. It may already be gone, or it may have been uploaded through a different image server.",
|
||||
};
|
||||
|
||||
// Used in: GiftedDetailsView.vue (deleteImage method - image deletion error)
|
||||
export const NOTIFY_IMAGE_DELETE_ERROR = {
|
||||
title: "Image Deletion Error",
|
||||
message: "There was an error deleting the image from the image server.",
|
||||
};
|
||||
|
||||
// Used in: NewEditProjectView.vue (validateLocation method - invalid location error)
|
||||
export const NOTIFY_PROJECT_INVALID_LOCATION = {
|
||||
title: "Invalid Location",
|
||||
|
||||
@@ -287,6 +287,13 @@ const MIGRATIONS = [
|
||||
ALTER TABLE settings ADD COLUMN smsNotificationsEnabled BOOLEAN DEFAULT FALSE;
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "011_add_imageServer_to_settings",
|
||||
sql: `
|
||||
-- Image API server URL; blank means DEFAULT_IMAGE_API_SERVER
|
||||
ALTER TABLE settings ADD COLUMN imageServer TEXT;
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ export type Settings = {
|
||||
hasBackedUpSeed?: boolean; // tracks whether the user has backed up their seed phrase
|
||||
hideRegisterPromptOnNewContact?: boolean;
|
||||
isRegistered?: boolean;
|
||||
// imageServer?: string; // if we want to allow modification then we should make image functionality optional -- or at least customizable
|
||||
imageServer?: string; // image API server URL
|
||||
lastName?: string; // deprecated - put all names in firstName
|
||||
|
||||
lastAckedOfferToUserJwtId?: string; // the last JWT ID for offer-to-user that they've acknowledged seeing
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface AccountSettings {
|
||||
notifyingReminderMessage?: string;
|
||||
notifyingReminderTime?: string;
|
||||
starredPlanHandleIds?: string[];
|
||||
imageServer?: string;
|
||||
reminderFastRolloverForTesting?: boolean;
|
||||
partnerApiServer?: string;
|
||||
profileImageUrl?: string;
|
||||
@@ -102,6 +103,8 @@ export interface SettingsState {
|
||||
activeDid: string;
|
||||
apiServer: string;
|
||||
apiServerInput: string;
|
||||
imageServer: string;
|
||||
imageServerInput: string;
|
||||
partnerApiServer: string;
|
||||
partnerApiServerInput: string;
|
||||
webPushServer: string;
|
||||
|
||||
@@ -638,6 +638,7 @@
|
||||
color="white"
|
||||
aria-hidden="true"
|
||||
></font-awesome>
|
||||
<span class="text-white">Unsavedaroonosity</span>
|
||||
</button>
|
||||
<div class="mt-2" role="group" aria-label="Quick server selection">
|
||||
<button
|
||||
@@ -735,6 +736,7 @@
|
||||
class="fa-fw"
|
||||
color="white"
|
||||
></font-awesome>
|
||||
<span class="text-white">Unsaved</span>
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@@ -781,6 +783,7 @@
|
||||
class="fa-fw"
|
||||
color="white"
|
||||
></font-awesome>
|
||||
<span class="text-white">Unsaved</span>
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@@ -813,11 +816,49 @@
|
||||
{{ DEFAULT_PARTNER_API_SERVER }}
|
||||
</span>
|
||||
|
||||
<div class="mt-2">
|
||||
<span class="text-slate-500 text-sm font-bold">Image Server URL</span>
|
||||
|
||||
<span class="text-sm">{{ DEFAULT_IMAGE_API_SERVER }}</span>
|
||||
<h2 class="text-slate-500 text-sm font-bold mb-2">Image Server URL</h2>
|
||||
<div class="px-3 py-4">
|
||||
<input
|
||||
v-model="imageServerInput"
|
||||
type="text"
|
||||
class="block w-full rounded border border-slate-400 px-3 py-2"
|
||||
/>
|
||||
<button
|
||||
v-if="imageServerInput != imageServer"
|
||||
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
|
||||
@click="onClickSaveImageServer()"
|
||||
>
|
||||
<font-awesome
|
||||
icon="floppy-disk"
|
||||
class="fa-fw"
|
||||
color="white"
|
||||
></font-awesome>
|
||||
<span class="text-white">Unsaved</span>
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="imageServerInput = AppConstants.PROD_IMAGE_API_SERVER"
|
||||
>
|
||||
Use Prod
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="imageServerInput = AppConstants.TEST_IMAGE_API_SERVER"
|
||||
>
|
||||
Use Test
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="imageServerInput = AppConstants.LOCAL_IMAGE_API_SERVER"
|
||||
>
|
||||
Use Local
|
||||
</button>
|
||||
</div>
|
||||
<span v-if="!imageServerInput" class="px-4 text-sm">
|
||||
When that setting is blank, this app will use the default image server
|
||||
URL:
|
||||
{{ DEFAULT_IMAGE_API_SERVER }}
|
||||
</span>
|
||||
|
||||
<div class="mt-2">
|
||||
<span class="text-slate-500 text-sm font-bold"
|
||||
@@ -1025,6 +1066,7 @@ import { logger } from "../utils/logger";
|
||||
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
|
||||
import { NOTIFY_IMAGE_DELETE_NOT_FOUND } from "@/constants/notifications";
|
||||
import { showSeedPhraseReminder } from "@/utils/seedPhraseReminder";
|
||||
import { AccountSettings, isApiError } from "@/interfaces/accountView";
|
||||
import {
|
||||
@@ -1122,6 +1164,8 @@ export default class AccountViewView extends Vue {
|
||||
searchBox: { name: string; bbox: BoundingBox } | null = null;
|
||||
partnerApiServer: string = DEFAULT_PARTNER_API_SERVER;
|
||||
partnerApiServerInput: string = DEFAULT_PARTNER_API_SERVER;
|
||||
imageServer: string = DEFAULT_IMAGE_API_SERVER;
|
||||
imageServerInput: string = DEFAULT_IMAGE_API_SERVER;
|
||||
passkeyExpirationDescription: string = "";
|
||||
passkeyExpirationMinutes: number = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
||||
previousPasskeyExpirationMinutes: number = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
||||
@@ -1354,6 +1398,8 @@ export default class AccountViewView extends Vue {
|
||||
this.partnerApiServer = settings.partnerApiServer || this.partnerApiServer;
|
||||
this.partnerApiServerInput =
|
||||
settings.partnerApiServer || this.partnerApiServerInput;
|
||||
this.imageServer = settings.imageServer || this.imageServer;
|
||||
this.imageServerInput = settings.imageServer || this.imageServerInput;
|
||||
this.profileImageUrl = settings.profileImageUrl;
|
||||
this.showContactGives = !!settings.showContactGivesInline;
|
||||
this.passkeyExpirationMinutes =
|
||||
@@ -2435,7 +2481,7 @@ export default class AccountViewView extends Vue {
|
||||
const imageResp = await fetchImageRateLimits(
|
||||
this.axios,
|
||||
did,
|
||||
this.DEFAULT_IMAGE_API_SERVER,
|
||||
this.imageServer,
|
||||
);
|
||||
|
||||
if (imageResp && imageResp.status === 200) {
|
||||
@@ -2470,7 +2516,7 @@ export default class AccountViewView extends Vue {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
did: did,
|
||||
apiServer: this.apiServer,
|
||||
imageServer: this.DEFAULT_IMAGE_API_SERVER,
|
||||
imageServer: this.imageServer,
|
||||
partnerApiServer: this.partnerApiServer,
|
||||
errorCode: axiosError?.response?.data?.error?.code,
|
||||
errorMessage: axiosError?.response?.data?.error?.message,
|
||||
@@ -2553,6 +2599,17 @@ export default class AccountViewView extends Vue {
|
||||
});
|
||||
}
|
||||
|
||||
async onClickSaveImageServer(): Promise<void> {
|
||||
await this.$saveSettings({
|
||||
imageServer: this.imageServerInput,
|
||||
});
|
||||
this.imageServer = this.imageServerInput;
|
||||
|
||||
await this.$saveUserSettings(this.activeDid, {
|
||||
imageServer: this.imageServer,
|
||||
});
|
||||
}
|
||||
|
||||
async onClickSavePushServer(): Promise<void> {
|
||||
await this.$saveSettings({
|
||||
webPushServer: this.webPushServerInput,
|
||||
@@ -2583,15 +2640,14 @@ export default class AccountViewView extends Vue {
|
||||
|
||||
async deleteImage(): Promise<void> {
|
||||
try {
|
||||
// Extract the image ID from the full URL
|
||||
const imageId = this.profileImageUrl?.split("/").pop();
|
||||
if (!imageId) {
|
||||
if (!this.profileImageUrl) {
|
||||
this.notify.error("Invalid image URL");
|
||||
return;
|
||||
}
|
||||
|
||||
// the image server looks the image up by its full URL
|
||||
const response = await this.axios.delete(
|
||||
this.apiServer + "/api/image/" + imageId,
|
||||
this.imageServer + "/image/" + encodeURIComponent(this.profileImageUrl),
|
||||
{ headers: await getHeaders(this.activeDid) },
|
||||
);
|
||||
if (response.status === 204) {
|
||||
@@ -2607,12 +2663,12 @@ export default class AccountViewView extends Vue {
|
||||
}
|
||||
} catch (error) {
|
||||
if (isApiError(error) && error.response?.status === 404) {
|
||||
// it already doesn't exist so we won't say anything to the user
|
||||
// Clear the local reference since the image is gone
|
||||
// still drop it from the profile, which is what the user asked for
|
||||
this.profileImageUrl = "";
|
||||
await this.$saveSettings({
|
||||
profileImageUrl: "",
|
||||
});
|
||||
this.notify.error(NOTIFY_IMAGE_DELETE_NOT_FOUND.message, TIMEOUTS.LONG);
|
||||
} else {
|
||||
this.notify.error(
|
||||
ACCOUNT_VIEW_CONSTANTS.ERRORS.IMAGE_DELETE_ERROR,
|
||||
|
||||
@@ -271,6 +271,8 @@ import {
|
||||
NOTIFY_GIFTED_DETAILS_CREATE_GIVE_ERROR,
|
||||
NOTIFY_GIFTED_DETAILS_GIFT_RECORDED,
|
||||
NOTIFY_GIFT_ERROR_NO_DESCRIPTION,
|
||||
NOTIFY_IMAGE_DELETE_ERROR,
|
||||
NOTIFY_IMAGE_DELETE_NOT_FOUND,
|
||||
} from "@/constants/notifications";
|
||||
|
||||
@Component({
|
||||
@@ -563,30 +565,33 @@ export default class GiftedDetails extends Vue {
|
||||
}
|
||||
try {
|
||||
const headers = await getHeaders(this.activeDid);
|
||||
const imageServer =
|
||||
(await this.$accountSettings()).imageServer || DEFAULT_IMAGE_API_SERVER;
|
||||
if (
|
||||
window.location.hostname === "localhost" &&
|
||||
!DEFAULT_IMAGE_API_SERVER.includes("localhost")
|
||||
!imageServer.includes("localhost")
|
||||
) {
|
||||
logger.log(
|
||||
"Using shared image API server, so only users on that server can play with images.",
|
||||
);
|
||||
}
|
||||
const response = await this.axios.delete(
|
||||
DEFAULT_IMAGE_API_SERVER + "/image/" + encodeURIComponent(imageUrl),
|
||||
imageServer + "/image/" + encodeURIComponent(imageUrl),
|
||||
{ headers },
|
||||
);
|
||||
if (response.status === 204) {
|
||||
// don't bother with a notification
|
||||
} else {
|
||||
logger.error("Problem deleting image:", response);
|
||||
this.notify.error(NOTIFY_IMAGE_DELETE_ERROR.message, TIMEOUTS.LONG);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error deleting image:", error);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((error as any)?.response?.status === 404) {
|
||||
logger.log("Image was already deleted:", error);
|
||||
this.notify.error(NOTIFY_IMAGE_DELETE_NOT_FOUND.message, TIMEOUTS.LONG);
|
||||
} else {
|
||||
logger.error("Failed to delete image from server:", error);
|
||||
this.notify.error(NOTIFY_IMAGE_DELETE_ERROR.message, TIMEOUTS.LONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,13 +335,14 @@ import {
|
||||
NotificationIface,
|
||||
} from "../constants/app";
|
||||
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers } from "../utils/notify";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "../utils/notify";
|
||||
import {
|
||||
NOTIFY_PROJECT_ACCOUNT_LOADING_ERROR,
|
||||
NOTIFY_PROJECT_RETRIEVAL_ERROR,
|
||||
NOTIFY_PROJECT_DELETE_IMAGE_CONFIRM,
|
||||
NOTIFY_PROJECT_DELETE_IMAGE_ERROR,
|
||||
NOTIFY_PROJECT_DELETE_IMAGE_GENERAL_ERROR,
|
||||
NOTIFY_IMAGE_DELETE_NOT_FOUND,
|
||||
NOTIFY_PROJECT_INVALID_LOCATION,
|
||||
NOTIFY_PROJECT_INVALID_START_DATE,
|
||||
NOTIFY_PROJECT_INVALID_END_DATE,
|
||||
@@ -617,9 +618,11 @@ export default class NewEditProjectView extends Vue {
|
||||
}
|
||||
try {
|
||||
const headers = (await getHeaders(this.activeDid)) as AxiosRequestHeaders;
|
||||
const imageServer =
|
||||
(await this.$accountSettings()).imageServer || DEFAULT_IMAGE_API_SERVER;
|
||||
if (
|
||||
window.location.hostname === "localhost" &&
|
||||
!DEFAULT_IMAGE_API_SERVER.includes("localhost")
|
||||
!imageServer.includes("localhost")
|
||||
) {
|
||||
logger.log(
|
||||
"Using shared image API server, so only users on that server can play with images.",
|
||||
@@ -627,9 +630,7 @@ export default class NewEditProjectView extends Vue {
|
||||
}
|
||||
|
||||
const response = await this.axios.delete(
|
||||
DEFAULT_IMAGE_API_SERVER +
|
||||
"/image/" +
|
||||
encodeURIComponent(this.imageUrl),
|
||||
imageServer + "/image/" + encodeURIComponent(this.imageUrl),
|
||||
{ headers },
|
||||
);
|
||||
if (response.status === 204) {
|
||||
@@ -645,12 +646,10 @@ export default class NewEditProjectView extends Vue {
|
||||
} catch (error) {
|
||||
logger.error("Error deleting image:", error);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((error as any).response.status === 404) {
|
||||
logger.log("The image was already deleted:", error);
|
||||
|
||||
if ((error as any)?.response?.status === 404) {
|
||||
// still drop it from the project, which is what the user asked for
|
||||
this.imageUrl = "";
|
||||
|
||||
// it already doesn't exist so we won't say anything to the user
|
||||
this.notify.error(NOTIFY_IMAGE_DELETE_NOT_FOUND.message, TIMEOUTS.LONG);
|
||||
} else {
|
||||
this.notify.error(NOTIFY_PROJECT_DELETE_IMAGE_GENERAL_ERROR.message);
|
||||
}
|
||||
|
||||
@@ -338,19 +338,19 @@ export default class SharedPhotoView extends Vue {
|
||||
);
|
||||
formData.append("claimType", imageType);
|
||||
|
||||
const imageServer =
|
||||
(await this.$accountSettings()).imageServer || DEFAULT_IMAGE_API_SERVER;
|
||||
if (
|
||||
window.location.hostname === "localhost" &&
|
||||
!DEFAULT_IMAGE_API_SERVER.includes("localhost")
|
||||
!imageServer.includes("localhost")
|
||||
) {
|
||||
logger.log(
|
||||
"Using shared image API server, so only users on that server can play with images.",
|
||||
);
|
||||
}
|
||||
const response = await axios.post(
|
||||
DEFAULT_IMAGE_API_SERVER + "/image",
|
||||
formData,
|
||||
{ headers },
|
||||
);
|
||||
const response = await axios.post(imageServer + "/image", formData, {
|
||||
headers,
|
||||
});
|
||||
if (response?.data?.url) {
|
||||
this.imageBlob = undefined;
|
||||
this.imageFileName = undefined;
|
||||
|
||||
Reference in New Issue
Block a user