Skip to content

Commit

Permalink
feat: upload data (#185)
Browse files Browse the repository at this point in the history
* use composables for session settings

* testing fetch methode

* encountered CORS Error

* added whitespace

* rewrite upload data composable a bit

* added API composable for ease of use

* implemented api post/get request

* only data should be shared for now

* using .env for url and token

* removing useless getters and setters

* removed testing code

* removed interface

* should fix issues

* updated post data format

* removed sanatization

---------

Co-authored-by: Kevin Beier <[email protected]>
  • Loading branch information
roman533 and Kevin Beier authored Jun 1, 2023
1 parent 927688a commit acd16d3
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 50 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_BASEURL=https://api.example.com
VITE_BEARERTOKEN=YOUR_BEARER_TOKEN
11 changes: 1 addition & 10 deletions src/components/LogOutput/LogOutput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { Vigad } from '@/proc/Vigad';
import { MatchedElement } from '@/proc/MatchedElement';
import useRunning from '@/composables/useRunning/useRunning';
const props = defineProps<{
Expand All @@ -43,16 +44,6 @@ const { isRunning } = useRunning();
*/
const vigad = ref(Vigad.getInstance());
// local interface otherwise typescript is complaining
interface MatchedElement {
match: {
index: number;
element: string;
};
rating: number;
timestamp?: string;
}
const matchedElementsIsEmpty = computed(() => {
return matchedElements.value.length === 0;
});
Expand Down
57 changes: 18 additions & 39 deletions src/components/SettingsPrompt/SettingsPrompt.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<v-list-item>
<v-list-item-title>
<v-text-field
v-model="accessToken"
v-model="sessionToken"
style="width: 450px"
class="pt-2"
variant="outlined"
Expand All @@ -76,7 +76,7 @@
v-bind="props"
icon="mdi-content-copy"
@click="
copyToClipboard(accessToken)
copyToClipboard(sessionToken)
"
></v-icon>
</template>
Expand Down Expand Up @@ -131,6 +131,7 @@
class="mr-4"
color="primary"
inset
:disabled="true"
></v-switch>
</template>
</v-list-item>
Expand All @@ -140,27 +141,28 @@
</template>

<script setup lang="ts">
import { Ref, ref, watch } from 'vue';
import useNotificationSystem from '@/composables/useNotificationSystem/useNotificationSystem';
import useClipboard from '@/composables/useClipboard/useClipboard';
import useTokenGenerator from '@/composables/useTokenGenerator/useTokenGenerator';
import { Ref, ref, watch } from 'vue';
import useUploadData from '@/composables/useUploadData/useUploadData';
import useSession from '@/composables/useSession/useSession';
/**
* Composables
*/
const { sessionToken, isSessionActive, startSession, stopSession } =
useSession();
const { writeClipboardText } = useClipboard();
const { defaultRules, generateValidToken } = useTokenGenerator();
const { streamData, streamRegexAndCaptureAreaSettings } = useUploadData();
/**
* Data
*/
const accessToken = ref('');
const isAccessTokenValid = ref(false);
const errorMessage: Ref<string[]> = ref([]);
const tokenVisibility = ref(false);
const isSessionActive = ref(false);
const streamData = ref(false);
const streamRegexAndCaptureAreaSettings = ref(false);
/**
* Dialog visibility
Expand All @@ -172,60 +174,37 @@ watch(
(value) => {
if (value) {
// generate token if empty
if (accessToken.value === '') {
if (sessionToken.value === '') {
regenerateAccessToken();
return;
} else {
// try to validate token if not empty
errorMessage.value = Object.values(defaultRules)
.map((rule) => rule(accessToken.value))
.map((rule) => rule(sessionToken.value))
.filter((value) => typeof value === 'string') as string[];
}
} else {
// reset validation state
const isValid = Object.values(defaultRules).every(
(rule) => rule(accessToken.value) === true
(rule) => rule(sessionToken.value) === true
);
if (isAccessTokenValid.value === isValid) {
errorMessage.value = Object.values(defaultRules)
.map((rule) => rule(accessToken.value))
.map((rule) => rule(sessionToken.value))
.filter((value) => typeof value === 'string') as string[];
}
}
}
);
watch(
() => accessToken.value,
() => sessionToken.value,
() => {
validate();
}
);
/**
* Start the session
*/
function startSession() {
isSessionActive.value = true;
// TODO: Start session functionality
useNotificationSystem().createNotification({
title: 'Session started',
});
}
/**
* Stop the session
*/
function stopSession() {
isSessionActive.value = false;
// TODO: Stop session functionality
useNotificationSystem().createNotification({
title: 'Session stopped',
type: 'info',
});
}
/**
* Function which will toggle the visibility of the access token
*/
Expand All @@ -237,21 +216,21 @@ function toggleTokenVisibility() {
* Function which will regenerate a new access token
*/
async function regenerateAccessToken() {
accessToken.value = generateValidToken();
sessionToken.value = generateValidToken();
}
/**
* Function which will validate the access token and notifies the user
*/
function validate() {
const isValid = Object.values(defaultRules).every(
(rule) => rule(accessToken.value) === true
(rule) => rule(sessionToken.value) === true
);
isAccessTokenValid.value = isValid;
errorMessage.value = Object.values(defaultRules)
.map((rule) => rule(accessToken.value))
.map((rule) => rule(sessionToken.value))
.filter((value) => typeof value === 'string') as string[];
if (!isValid && isSessionActive.value) {
Expand All @@ -274,7 +253,7 @@ function validate() {
function validateAccessToken() {
// check if the access token is valid via the defaultRules
const isValid = Object.values(defaultRules).every(
(rule) => rule(accessToken.value) === true
(rule) => rule(sessionToken.value) === true
);
if (isAccessTokenValid.value === isValid) {
Expand Down
80 changes: 80 additions & 0 deletions src/composables/useAPI/useAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import useNotificationSystem from '@/composables/useNotificationSystem/useNotificationSystem';

/**
* API usage composable
*/
export default function useAPI() {
/**
* Perform a GET request to the specified API endpoint.
* @param {string} endpoint - The API endpoint to fetch data from.
* @returns {Promise<object>} A Promise that resolves to the API response.
* @throws {Error} If the request fails or the response is not OK.
*/
const get = async (endpoint: string): Promise<object> => {
try {
const url = `${import.meta.env.VITE_BASEURL}/${endpoint}`;
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${import.meta.env.VITE_BEARERTOKEN}`,
},
});

if (!response.ok) {
useNotificationSystem().createErrorNotification({
title: 'Failed to fetch data',
});
throw new Error('Failed to fetch data');
}

return await response.json();
} catch (error) {
useNotificationSystem().createErrorNotification({
title: 'Error occurred while fetching data',
message: `${error}`,
});
throw error;
}
};

/**
* Perform a POST request to the specified API endpoint.
* @param {string} endpoint - The API endpoint to post data to.
* @param {object} data - The data to be posted to the API.
* @returns {Promise<object>} A Promise that resolves to the API response.
* @throws {Error} If the request fails or the response is not OK.
*/
const post = async (endpoint: string, data: object): Promise<object> => {
try {
const url = `${import.meta.env.VITE_BASEURL}/${endpoint}`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${import.meta.env.VITE_BEARERTOKEN}`,
},
body: JSON.stringify(data),
});

if (!response.ok) {
useNotificationSystem().createErrorNotification({
title: 'Failed to post data',
});
throw new Error('Failed to post data');
}

return await response.json();
} catch (error) {
useNotificationSystem().createErrorNotification({
title: 'Error occurred while posting data',
message: `${error}`,
});
throw error;
}
};

return {
get,
post,
};
}
36 changes: 36 additions & 0 deletions src/composables/useSession/useSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ref, Ref } from 'vue';
import useNotificationSystem from '@/composables/useNotificationSystem/useNotificationSystem';

const sessionToken = ref('');
// Reactive variable to track session status
const isSessionActive: Ref<boolean> = ref(false);

export default function useSession() {
/**
* Starts the session
*/
const startSession = (): void => {
isSessionActive.value = true;
useNotificationSystem().createNotification({
title: 'Session started',
});
};

/**
* Stops the session
*/
const stopSession = (): void => {
isSessionActive.value = false;
useNotificationSystem().createNotification({
title: 'Session stopped',
type: 'info',
});
};

return {
sessionToken,
isSessionActive,
startSession,
stopSession,
};
}
14 changes: 14 additions & 0 deletions src/composables/useUploadData/useUploadData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ref } from 'vue';

const streamData = ref(false);
const streamRegexAndCaptureAreaSettings = ref(false);

/**
* Uploadable data composable
*/
export default function useUploadData() {
return {
streamData,
streamRegexAndCaptureAreaSettings,
};
}
11 changes: 11 additions & 0 deletions src/proc/MatchedElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Interface for matched elements
*/
export interface MatchedElement {
rating: number;
match: {
index: number;
element: string;
};
timestamp?: string;
}
45 changes: 44 additions & 1 deletion src/proc/Vigad.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { CaptureArea } from './CaptureArea';
import { RegexHandler } from './regex/RegexHandler';
import { TesseractHandler } from './TesseractHandler';
import { MatchedElement } from './MatchedElement';
import useSession from '@/composables/useSession/useSession';
import useUploadData from '@/composables/useUploadData/useUploadData';
import useAPI from '@/composables/useAPI/useAPI';
import useStreamHandler from '@/composables/useStreamHandler/useStreamHandler';

export class Vigad {
Expand Down Expand Up @@ -105,7 +109,7 @@ export class Vigad {

this.tesseractHandler.run(
currentSelectedSource.value,
(result: { ca_id: number; data: string }[]) => {
async (result: { ca_id: number; data: string }[]) => {
result.forEach(
(value: { ca_id: number; data: string }) => {
const ca = this.getCaptureArea(value.ca_id);
Expand Down Expand Up @@ -153,6 +157,45 @@ export class Vigad {
}
}
);
// Upload Data to the server
const { sessionToken, isSessionActive } = useSession();
const { post } = useAPI();
const {
streamData,
streamRegexAndCaptureAreaSettings,
} = useUploadData();

// Check whether the user streamData or streamRegexAndCaptureAreaSettings is enabled and if the session is active at all
if (
isSessionActive.value &&
(streamData.value ||
streamRegexAndCaptureAreaSettings.value)
) {
for (let i = 0; i < result.length; i++) {
const caBestMatch: MatchedElement =
this.getCaptureArea(result[i].ca_id)
.getRegexGroups()[0]
.getValueRegex()
.getLastBestMatch();

await post(
`session/${encodeURIComponent(
sessionToken.value
)}/data/ca/${result[i].ca_id}`,
{
rating: caBestMatch.rating,
match: {
index: caBestMatch.match.index,
element: result[i].data,
},
}
);
}
} else {
console.log(
'Session is not active or streamData/streamRegexAndCaptureAreaSettings is not enabled'
);
}
},
this.previewWidth,
this.previewHeight
Expand Down

0 comments on commit acd16d3

Please sign in to comment.