diff --git a/gen-api.sh b/gen-api.sh new file mode 100755 index 0000000..30c7ee3 --- /dev/null +++ b/gen-api.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +OPENAPI_URL="${OPENAPI_URL:-http://127.0.0.1:7300/openapi.json}" +SPEC_FILE="openapi.fetched.json" +OUTPUT_DIR="src/generated/typescript" + +cleanup() { + rm -f "$SPEC_FILE" +} + +trap cleanup EXIT + +curl --fail --silent --show-error \ + --header "Accept: application/json" \ + "$OPENAPI_URL" \ + --output "$SPEC_FILE" + +npx openapi-typescript-codegen \ + --input "$SPEC_FILE" \ + --output "$OUTPUT_DIR" \ No newline at end of file diff --git a/package.json b/package.json index 0ff5699..4fa12b6 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,7 @@ "scripts": { "dev": "vite", "build": "vue-tsc -b && vite build", - "preview": "vite preview", - "genapi": "npx openapi-typescript-codegen --input http://127.0.0.1:7300/openapi.json --output ./src/generated/typescript" + "preview": "vite preview" }, "dependencies": { "@stellar/freighter-api": "^4.1.0", diff --git a/src/components/AmendmentCarousel.vue b/src/components/AmendmentCarousel.vue index 75a093e..83180e0 100644 --- a/src/components/AmendmentCarousel.vue +++ b/src/components/AmendmentCarousel.vue @@ -51,8 +51,8 @@ import AmendmentModal from "./modals/AmendmentModal.vue"; import {getChipClass} from "../utils/amendment.ts"; import {AmendmentService} from "../generated/typescript"; -const props = defineProps<{issueId?: number}>(); -const {issueId} = toRefs(props); +const props = defineProps<{proposalId?: number}>(); +const {proposalId} = toRefs(props); const offset = ref(0); const amendments = ref(); @@ -65,8 +65,8 @@ const showAmendment = async (amendmentId: number, status: string) => { } const getAmendments = async () => { - if (!props.issueId) return; - const resp = await AmendmentService.listAmendments(props.issueId) + if (!props.proposalId) return; + const resp = await AmendmentService.listAmendments(props.proposalId) amendments.value = resp.amendments; } @@ -111,7 +111,7 @@ const scrolledMax = () => { const numberOfAmendments = computed(() => amendments.value?.length || 0); -watch(issueId, async (newVal) => { +watch(proposalId, async (newVal) => { if (!newVal) return; await getAmendments(); offset.value = 0; diff --git a/src/components/BrandBar.vue b/src/components/BrandBar.vue index 1e24a9a..ef937f8 100644 --- a/src/components/BrandBar.vue +++ b/src/components/BrandBar.vue @@ -1,7 +1,8 @@ - - - - \ No newline at end of file diff --git a/src/components/PostBuilder.vue b/src/components/PostBuilder.vue index bdb0f4d..9cfc41a 100644 --- a/src/components/PostBuilder.vue +++ b/src/components/PostBuilder.vue @@ -22,19 +22,18 @@ import {Editor, EditorContent} from '@tiptap/vue-3' import StarterKit from '@tiptap/starter-kit' import {onBeforeUnmount} from 'vue' -import Underline from '@tiptap/extension-underline' import TurndownService from 'turndown'; +import {ProposalParagraph} from "../types/proposal.ts"; const turndownService = new TurndownService(); const emit = defineEmits<{ - 'update:content': [content: string] + 'update:content': [content: ProposalParagraph[]], }>() const editor = new Editor({ extensions: [ StarterKit, - Underline, ], content: '', editorProps: { @@ -45,7 +44,8 @@ const editor = new Editor({ onUpdate: ({ editor }) => { const html = editor.getHTML(); const content = convertEditorContentToMarkdown(html); - emit('update:content', content); + const paragraphs = content.split('\n').map(p => p.trim()).filter(p => p.length > 0).map((p, i) => ({paragraphText: p, index: i})); + emit('update:content', paragraphs); } }) diff --git a/src/components/ProposalContainer.vue b/src/components/ProposalContainer.vue new file mode 100644 index 0000000..14c48f5 --- /dev/null +++ b/src/components/ProposalContainer.vue @@ -0,0 +1,74 @@ + + + + + \ No newline at end of file diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index 365651f..fb15cca 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -1,11 +1,11 @@ + + + + \ No newline at end of file diff --git a/src/components/icons/Stack.vue b/src/components/icons/Stack.vue new file mode 100644 index 0000000..e9942a4 --- /dev/null +++ b/src/components/icons/Stack.vue @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/src/components/modals/FilterModal.vue b/src/components/modals/FilterModal.vue index 30360ef..c3763f9 100644 --- a/src/components/modals/FilterModal.vue +++ b/src/components/modals/FilterModal.vue @@ -20,7 +20,7 @@ - - \ No newline at end of file diff --git a/src/components/modals/Modal.vue b/src/components/modals/Modal.vue index 08984a6..f8e1b65 100644 --- a/src/components/modals/Modal.vue +++ b/src/components/modals/Modal.vue @@ -1,44 +1,144 @@ \ No newline at end of file diff --git a/src/composables/useFetch.ts b/src/composables/useFetch.ts deleted file mode 100644 index 790e898..0000000 --- a/src/composables/useFetch.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @deprecated This method is deprecated. - */ -export const useFetch = () => { - interface FetchConfig { - baseUrl: string; - headers: HeadersInit; - } - - interface ApiResponse { - data: T; - status: number; - } - - const DEFAULT_CONFIG: FetchConfig = { - baseUrl: '/api/v1', - headers: { - 'Content-Type': 'application/json' - } - }; - - const buildQueryString = (params?: Record): string => { - if (!params) return ''; - return '?' + Object.entries(params) - .map(([key, value]) => `${key}=${value}`) - .join('&'); - }; - - /** - * @deprecated This function is deprecated. - */ - const createRequest = async ( - endpoint: string, - method: 'GET' | 'POST', - options: RequestInit = {} - ): Promise> => { - const response = await fetch(`${DEFAULT_CONFIG.baseUrl}${endpoint}`, { - method, - mode: 'cors', - headers: new Headers(DEFAULT_CONFIG.headers), - ...options - }); - - const data = await response.json(); - return {data, status: response.status}; - }; - - /** - * @deprecated This function is deprecated. - */ - const get = async ( - endpoint: string, - queryParams?: Record - ): Promise => { - const query = buildQueryString(queryParams); - const response = await createRequest(`${endpoint}${query}`, 'GET'); - return response.data; - }; - - /** - * @deprecated This function is deprecated. - */ - const post = async (endpoint: string, payload: T): Promise => { - const response = await createRequest(endpoint, 'POST', { - body: JSON.stringify(payload) - }); - return response.data; - }; - - return { get, post } -} \ No newline at end of file diff --git a/src/generated/typescript/core/OpenAPI.ts b/src/generated/typescript/core/OpenAPI.ts index 061313b..0cfae5d 100644 --- a/src/generated/typescript/core/OpenAPI.ts +++ b/src/generated/typescript/core/OpenAPI.ts @@ -20,8 +20,8 @@ export type OpenAPIConfig = { }; export const OpenAPI: OpenAPIConfig = { - BASE: 'http://localhost:7300', - VERSION: '1.0.0', + BASE: '', + VERSION: '0.1.0', WITH_CREDENTIALS: false, CREDENTIALS: 'include', TOKEN: undefined, diff --git a/src/generated/typescript/index.ts b/src/generated/typescript/index.ts index 373df0d..4bc27cd 100644 --- a/src/generated/typescript/index.ts +++ b/src/generated/typescript/index.ts @@ -7,23 +7,17 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise'; export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; +export type { AddAmendmentRequest } from './models/AddAmendmentRequest'; export type { AddProposalRequest } from './models/AddProposalRequest'; export type { FreighterLoginRequest } from './models/FreighterLoginRequest'; -export type { ListProposalsResponse } from './models/ListProposalsResponse'; export type { LoginRequest } from './models/LoginRequest'; -export type { ProposalItem } from './models/ProposalItem'; -export type { ProposalList } from './models/ProposalList'; +export type { Proposal } from './models/Proposal'; +export type { ProposalParagraph } from './models/ProposalParagraph'; +export type { ProposalParagraphRequest } from './models/ProposalParagraphRequest'; +export type { ProposalWithParagraphs } from './models/ProposalWithParagraphs'; +export type { ProposalWithSummary } from './models/ProposalWithSummary'; export type { RegisterRequest } from './models/RegisterRequest'; -export type { VisitProposalRequest } from './models/VisitProposalRequest'; - -export { $AddProposalRequest } from './schemas/$AddProposalRequest'; -export { $FreighterLoginRequest } from './schemas/$FreighterLoginRequest'; -export { $ListProposalsResponse } from './schemas/$ListProposalsResponse'; -export { $LoginRequest } from './schemas/$LoginRequest'; -export { $ProposalItem } from './schemas/$ProposalItem'; -export { $ProposalList } from './schemas/$ProposalList'; -export { $RegisterRequest } from './schemas/$RegisterRequest'; -export { $VisitProposalRequest } from './schemas/$VisitProposalRequest'; +export type { SelectableProposal } from './models/SelectableProposal'; export { AmendmentService } from './services/AmendmentService'; export { AuthService } from './services/AuthService'; diff --git a/src/generated/typescript/models/ListProposalsResponse.ts b/src/generated/typescript/models/AddAmendmentRequest.ts similarity index 53% rename from src/generated/typescript/models/ListProposalsResponse.ts rename to src/generated/typescript/models/AddAmendmentRequest.ts index 5fb48de..f366c10 100644 --- a/src/generated/typescript/models/ListProposalsResponse.ts +++ b/src/generated/typescript/models/AddAmendmentRequest.ts @@ -2,8 +2,10 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ProposalList } from './ProposalList'; -export type ListProposalsResponse = { - proposals?: ProposalList; +export type AddAmendmentRequest = { + content: string; + creator: string; + name: string; + proposalId: number; }; diff --git a/src/generated/typescript/models/AddProposalRequest.ts b/src/generated/typescript/models/AddProposalRequest.ts index 24bfc6e..12a097a 100644 --- a/src/generated/typescript/models/AddProposalRequest.ts +++ b/src/generated/typescript/models/AddProposalRequest.ts @@ -2,9 +2,10 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { ProposalParagraphRequest } from './ProposalParagraphRequest'; export type AddProposalRequest = { creator: string; - description: string; name: string; + paragraphs: Array; }; diff --git a/src/generated/typescript/models/Proposal.ts b/src/generated/typescript/models/Proposal.ts new file mode 100644 index 0000000..2f22944 --- /dev/null +++ b/src/generated/typescript/models/Proposal.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type Proposal = { + createdAt: string; + creator: string; + name: string; + updatedAt: string; +}; + diff --git a/src/generated/typescript/models/VisitProposalRequest.ts b/src/generated/typescript/models/ProposalParagraph.ts similarity index 51% rename from src/generated/typescript/models/VisitProposalRequest.ts rename to src/generated/typescript/models/ProposalParagraph.ts index 53c0d87..4bf0854 100644 --- a/src/generated/typescript/models/VisitProposalRequest.ts +++ b/src/generated/typescript/models/ProposalParagraph.ts @@ -2,12 +2,9 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export type VisitProposalRequest = { - method?: string; - path?: string; +export type ProposalParagraph = { + index: number; + paragraphText: string; proposalId: number; - queryString?: string; - referrer?: string; - statusCode?: number; }; diff --git a/src/generated/typescript/models/ProposalList.ts b/src/generated/typescript/models/ProposalParagraphRequest.ts similarity index 57% rename from src/generated/typescript/models/ProposalList.ts rename to src/generated/typescript/models/ProposalParagraphRequest.ts index 1956b3a..afa91f9 100644 --- a/src/generated/typescript/models/ProposalList.ts +++ b/src/generated/typescript/models/ProposalParagraphRequest.ts @@ -2,5 +2,8 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ProposalItem } from './ProposalItem'; -export type ProposalList = Array; +export type ProposalParagraphRequest = { + index: number; + paragraphText: string; +}; + diff --git a/src/generated/typescript/models/ProposalWithParagraphs.ts b/src/generated/typescript/models/ProposalWithParagraphs.ts new file mode 100644 index 0000000..a4d8a18 --- /dev/null +++ b/src/generated/typescript/models/ProposalWithParagraphs.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProposalParagraph } from './ProposalParagraph'; +export type ProposalWithParagraphs = { + createdAt: string; + creator: string; + id: number; + name: string; + paragraphs: Array; + updatedAt?: string | null; +}; + diff --git a/src/generated/typescript/models/ProposalItem.ts b/src/generated/typescript/models/ProposalWithSummary.ts similarity index 57% rename from src/generated/typescript/models/ProposalItem.ts rename to src/generated/typescript/models/ProposalWithSummary.ts index 3452ced..fdcdb70 100644 --- a/src/generated/typescript/models/ProposalItem.ts +++ b/src/generated/typescript/models/ProposalWithSummary.ts @@ -2,15 +2,12 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export type ProposalItem = { - cid: string; - createdAt?: string | null; +export type ProposalWithSummary = { + createdAt: string; creator: string; id: number; - isCurrent: boolean; name: string; - previousCid?: string | null; - summary?: string | null; + summary: string; updatedAt?: string | null; }; diff --git a/src/generated/typescript/models/SelectableProposal.ts b/src/generated/typescript/models/SelectableProposal.ts new file mode 100644 index 0000000..326d288 --- /dev/null +++ b/src/generated/typescript/models/SelectableProposal.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type SelectableProposal = { + createdAt: string; + creator: string; + id: number; + name: string; + updatedAt?: string | null; +}; + diff --git a/src/generated/typescript/services/AmendmentService.ts b/src/generated/typescript/services/AmendmentService.ts index 71bac52..c1be078 100644 --- a/src/generated/typescript/services/AmendmentService.ts +++ b/src/generated/typescript/services/AmendmentService.ts @@ -2,57 +2,54 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { AddAmendmentRequest } from '../models/AddAmendmentRequest'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class AmendmentService { /** - * Create an amendment * @param requestBody - * @returns any OK + * @returns any CID of the added amendment * @throws ApiError */ - public static createAmendment( - requestBody: Record, + public static addAmendment( + requestBody: AddAmendmentRequest, ): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/amendment/add', body: requestBody, mediaType: 'application/json', + errors: { + 400: `Bad request`, + 500: `Internal server error`, + }, }); } /** - * Get an amendment - * @param id - * @returns any OK + * @returns any Amendment details * @throws ApiError */ - public static getAmendment( - id: number, - ): CancelablePromise> { + public static getAmendment(): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/amendment/get', - query: { - 'id': id, + errors: { + 400: `Bad request`, + 500: `Internal server error`, }, }); } /** - * List amendments - * @param proposalId - * @returns any OK + * @returns any List of amendments for a proposal * @throws ApiError */ - public static listAmendments( - proposalId: number, - ): CancelablePromise> { + public static listAmendments(): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/amendments/list', - query: { - 'proposalId': proposalId, + errors: { + 500: `Internal server error`, }, }); } diff --git a/src/generated/typescript/services/AuthService.ts b/src/generated/typescript/services/AuthService.ts index 6618200..56b6770 100644 --- a/src/generated/typescript/services/AuthService.ts +++ b/src/generated/typescript/services/AuthService.ts @@ -10,71 +10,76 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class AuthService { /** - * Login with username/password * @param requestBody - * @returns any OK + * @returns any User logged in successfully * @throws ApiError */ - public static loginUser( + public static login( requestBody: LoginRequest, - ): CancelablePromise> { + ): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/auth/login', body: requestBody, mediaType: 'application/json', errors: { + 400: `Bad request`, 401: `Unauthorized`, + 500: `Internal server error`, }, }); } /** - * Login with Stellar (Freighter) * @param requestBody - * @returns any OK + * @returns any User logged in successfully * @throws ApiError */ public static loginFreighter( requestBody: FreighterLoginRequest, - ): CancelablePromise> { + ): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/auth/login/freighter', body: requestBody, mediaType: 'application/json', errors: { + 400: `Bad request`, 401: `Unauthorized`, + 500: `Internal server error`, }, }); } /** - * Get freighter login nonce - * @returns any OK + * @returns any User logged in successfully * @throws ApiError */ - public static getFreighterNonce(): CancelablePromise> { + public static getNonce(): CancelablePromise { return __request(OpenAPI, { - method: 'GET', + method: 'POST', url: '/api/v1/auth/nonce', + errors: { + 400: `Bad request`, + 401: `Unauthorized`, + 500: `Internal server error`, + }, }); } /** - * Register a user * @param requestBody - * @returns any Created + * @returns any User registered successfully * @throws ApiError */ - public static registerUser( + public static register( requestBody: RegisterRequest, - ): CancelablePromise> { + ): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/auth/register', body: requestBody, mediaType: 'application/json', errors: { - 400: `Bad Request`, - 409: `Conflict`, + 400: `Bad request`, + 500: `Internal server error`, }, }); } diff --git a/src/generated/typescript/services/ProposalService.ts b/src/generated/typescript/services/ProposalService.ts index a2ab0ed..a52ff48 100644 --- a/src/generated/typescript/services/ProposalService.ts +++ b/src/generated/typescript/services/ProposalService.ts @@ -3,75 +3,59 @@ /* tslint:disable */ /* eslint-disable */ import type { AddProposalRequest } from '../models/AddProposalRequest'; -import type { ListProposalsResponse } from '../models/ListProposalsResponse'; -import type { VisitProposalRequest } from '../models/VisitProposalRequest'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class ProposalService { /** - * Create a proposal * @param requestBody - * @returns any OK + * @returns any Proposal added successfully * @throws ApiError */ - public static createProposal( + public static addProposal( requestBody: AddProposalRequest, - ): CancelablePromise> { + ): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/api/v1/proposal/add', body: requestBody, mediaType: 'application/json', errors: { - 400: `Bad Request`, + 400: `Bad request`, + 500: `Failed to add proposal`, }, }); } /** - * Get a proposal by id * @param id - * @returns any OK + * @returns any Proposal retrieved successfully * @throws ApiError */ public static getProposal( id: number, - ): CancelablePromise> { + ): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/proposal/get', query: { 'id': id, }, + errors: { + 400: `Invalid proposalId`, + 500: `Failed to retrieve proposal`, + }, }); } /** - * Record a proposal visit - * @param requestBody - * @returns any Created - * @throws ApiError - */ - public static recordProposalVisit( - requestBody: VisitProposalRequest, - ): CancelablePromise> { - return __request(OpenAPI, { - method: 'POST', - url: '/api/v1/proposal/visit', - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * List proposals * @param offset * @param limit - * @returns ListProposalsResponse OK + * @returns any Returns a list of proposals * @throws ApiError */ public static listProposals( offset: number, limit: number, - ): CancelablePromise { + ): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/api/v1/proposals/list', @@ -79,6 +63,9 @@ export class ProposalService { 'offset': offset, 'limit': limit, }, + errors: { + 500: `Internal server error`, + }, }); } } diff --git a/src/main.ts b/src/main.ts index 817509b..61e5365 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,10 @@ import App from './App.vue' import router from './router' import { createPinia } from 'pinia' import './index.css' +import { OpenAPI } from './generated/typescript' + +OpenAPI.BASE = '' +OpenAPI.HEADERS = localStorage.getItem("token") ? {"Authorization": "Bearer " + localStorage.getItem("token")} : {}; const pinia = createPinia(); createApp(App).use(pinia).use(router).mount('#app') diff --git a/src/types/issue.ts b/src/types/proposal.ts similarity index 54% rename from src/types/issue.ts rename to src/types/proposal.ts index 18dc0f0..e67838d 100644 --- a/src/types/issue.ts +++ b/src/types/proposal.ts @@ -1,5 +1,5 @@ // Base interface for common issue properties -export interface BaseIssue { +export interface BaseProposal { id: number; name: string; cid: string; @@ -7,7 +7,7 @@ export interface BaseIssue { } // Main proposal issue interface extending base -export interface ProposalIssue extends BaseIssue { +export interface Proposal extends BaseProposal { summary: string; isCurrent: boolean; previousCid?: string; @@ -21,39 +21,43 @@ interface ContentHolder { } // Type for issue content combining base fields and content -export type ProposalContent = BaseIssue & ContentHolder; +export type ProposalContent = BaseProposal & ContentHolder; -export interface ListIssuesResponse { - proposals: ProposalIssue[] +export interface ListProposalsResponse { + proposals: Proposal[] } -export interface GetIssueResponse { +export interface GetProposalResponse { proposal: ProposalContent; } -export interface AddIssueRequest { +export interface AddProposalRequest { name: string; description: string; creator: string; } -export interface AddIssueResponse { +export interface AddProposalResponse { cid: string; } -export interface VoteIssueRequest { - issue_id: string; +export interface VoteProposalRequest { + proposal_id: string; wallet: string; vote: 'Positive' | 'Negative'; } -export interface VoteIssueResponse { - issue_id: string; +export interface VoteProposalResponse { + proposal_id: string; equity: number; positive: boolean; } -export interface IssueSelection { +export interface ProposalSelection { proposal: ProposalContent; - cid: string; +} + +export interface ProposalParagraph { + paragraphText: string; + index: number; } \ No newline at end of file diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index ea7fdcf..53a5f23 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -1,19 +1,12 @@