Refactor tutorial management and add AI image model functionality
- Updated application.properties for local database configuration. - Introduced DateTimeHelper for timestamp parsing. - Added MyAIImageModel and MyImageOptions for AI image processing. - Enhanced Tutorial model to include created and modified timestamps. - Implemented tutorial editing component with improved UI and functionality. - Updated user API service to handle tutorial retrieval and updates. - Refactored tutorials list component to support selection and batch deletion.
This commit is contained in:
Binary file not shown.
@@ -1,6 +1,47 @@
|
||||
export class Tutorial {
|
||||
isSelected?: boolean;
|
||||
id?: any;
|
||||
title?: string;
|
||||
description?: string;
|
||||
published?: boolean;
|
||||
created?: Date;
|
||||
modified?: Date;
|
||||
isEdit?: boolean;
|
||||
}
|
||||
|
||||
export const TutorialColumns = [
|
||||
{
|
||||
key: 'isSelected',
|
||||
type: 'isSelected',
|
||||
label: '',
|
||||
},
|
||||
{
|
||||
key: 'title',
|
||||
type: 'text',
|
||||
label: 'Title',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'published',
|
||||
type: 'boolean',
|
||||
label: 'Is Published',
|
||||
},
|
||||
{
|
||||
key: 'created',
|
||||
type: 'datetime',
|
||||
label: 'Created Date',
|
||||
required: true,
|
||||
|
||||
},
|
||||
{
|
||||
key: 'modified',
|
||||
type: 'datetime',
|
||||
label: 'Modified Date',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'isEdit',
|
||||
type: 'isEdit',
|
||||
label: '',
|
||||
}
|
||||
];
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Edit tutorial</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content >
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label>Title</mat-label>
|
||||
<input matInput required
|
||||
[(ngModel)]="tutorial.title">
|
||||
</mat-form-field>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Markdown text">
|
||||
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
|
||||
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Result">
|
||||
<markdown class="preview" [data]="tutorial.description"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Example">
|
||||
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
|
||||
<button matButton (click)="updateTutorial()">Save</button>
|
||||
@if (hasError){
|
||||
<mat-error>
|
||||
{{errorMessage}}
|
||||
</mat-error>
|
||||
}
|
||||
@if(submitted) {
|
||||
<h4>Tutorial was submitted successfully!</h4>
|
||||
<button matButton routerLink="../tutorial-add">Add new tutorial</button>
|
||||
<button matButton routerLink="../tutorials-list">Back to list</button>
|
||||
}
|
||||
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
.submit-form {
|
||||
max-width: 400px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
mat-card{
|
||||
margin: 20px;
|
||||
}
|
||||
mat-card-title{
|
||||
color: cyan;
|
||||
}
|
||||
|
||||
.example-form {
|
||||
min-width: 150px;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.example-full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.variable-binding,
|
||||
.variable-textarea {
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
.variable-textarea {
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.07);
|
||||
min-height: 420px;
|
||||
padding: 8px;
|
||||
transition: all 300ms ease-out;
|
||||
}
|
||||
|
||||
.variable-textarea:hover {
|
||||
box-shadow: 0 6px 12px 3px rgba(0,0,0,.09),
|
||||
0 2px 3px 1px rgba(0,0,0,.06);
|
||||
}
|
||||
|
||||
.variable-binding {
|
||||
display: block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.preview {
|
||||
/* display: block;
|
||||
float: right;*/
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialEditComponent } from './tutorial-edit.component';
|
||||
|
||||
describe('TutorialEditComponent', () => {
|
||||
let component: TutorialEditComponent;
|
||||
let fixture: ComponentFixture<TutorialEditComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TutorialEditComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TutorialEditComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {MarkdownComponent} from 'ngx-markdown';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||
import {MatError, MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
||||
import {NgIf} from '@angular/common';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {UserApiService} from '../user-api.service';
|
||||
import {ActivatedRoute, Router, RouterLink} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorial-edit.component',
|
||||
imports: [
|
||||
MarkdownComponent,
|
||||
MatButton,
|
||||
MatCard,
|
||||
MatCardActions,
|
||||
MatCardContent,
|
||||
MatCardHeader,
|
||||
MatFormField,
|
||||
MatInput,
|
||||
MatLabel,
|
||||
MatTab,
|
||||
MatTabGroup,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
MatFormField,
|
||||
RouterLink,
|
||||
MatError
|
||||
],
|
||||
templateUrl: './tutorial-edit.component.html',
|
||||
styleUrl: './tutorial-edit.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class TutorialEditComponent {
|
||||
tutorial: Tutorial = new Tutorial();
|
||||
submitted = false;
|
||||
hasError = false;
|
||||
errorMessage = '';
|
||||
markdown = `## Markdown __rulez__!
|
||||
---
|
||||
|
||||
### Syntax highlight
|
||||
\`\`\`typescript
|
||||
const language = 'typescript';
|
||||
\`\`\`
|
||||
|
||||
### Lists
|
||||
1. Ordered list
|
||||
2. Another bullet point
|
||||
- Unordered list
|
||||
- Another unordered bullet
|
||||
|
||||
### Blockquote
|
||||
> Blockquote to the max`;
|
||||
private id: string | null | undefined;
|
||||
|
||||
constructor(private userApiService: UserApiService, private route: ActivatedRoute) {
|
||||
|
||||
this.route.queryParams
|
||||
.subscribe(params => {
|
||||
console.log(params);
|
||||
this.id = params['id'];
|
||||
console.log(this.id);
|
||||
});
|
||||
|
||||
this.userApiService.getTutorial(this.id).subscribe(
|
||||
data=>{
|
||||
this.tutorial = data;
|
||||
}
|
||||
);
|
||||
}
|
||||
updateTutorial(): void {
|
||||
this.userApiService.update(this.id, this.tutorial)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.submitted = true;
|
||||
this.hasError = false;
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
|
||||
this.errorMessage = error.error.message;
|
||||
|
||||
this.hasError = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
+143
-24
@@ -1,29 +1,148 @@
|
||||
<p>tutorials-list.component works!</p>
|
||||
<article class="table-header">
|
||||
<button
|
||||
class="button-remove-rows"
|
||||
mat-button
|
||||
(click)="removeSelectedRows()"
|
||||
>
|
||||
Remove Rows
|
||||
</button>
|
||||
<!--<button class="button-add-row" mat-button (click)="addRow()">
|
||||
Add Row
|
||||
</button>-->
|
||||
<button mat-raised-button routerLink="../tutorial-add">Add tutorial</button>
|
||||
|
||||
<button mat-raised-button routerLink="../tutorial-add">Add tutorial</button>
|
||||
</article>
|
||||
<table mat-table [dataSource]="dataSource">
|
||||
@for (column of columnsSchema; track column){
|
||||
<ng-container [matColumnDef]="column.key">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
@switch (column.key) {
|
||||
@case ('isSelected') {
|
||||
<mat-checkbox
|
||||
(change)="selectAll($event)"
|
||||
[checked]="isAllSelected()"
|
||||
[indeterminate]="!isAllSelected() && isAnySelected()"
|
||||
></mat-checkbox>
|
||||
}
|
||||
@default {
|
||||
{{ column.label }}
|
||||
}
|
||||
}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<!-- @if (element.isEdit) {-->
|
||||
@switch (column.type) {
|
||||
@case ('isSelected') {
|
||||
<mat-checkbox
|
||||
(change)="element.isSelected = $event.checked"
|
||||
[checked]="element.isSelected"
|
||||
></mat-checkbox>
|
||||
}
|
||||
@case('isEdit') {
|
||||
<div class="btn-edit" >
|
||||
<button mat-button routerLink='../tutorial-edit' [queryParams]="{id:element.id}" (click)="element.isEdit = !element.isEdit">
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
class="button-remove"
|
||||
(click)="removeRow(element.id)"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@case ('boolean') {
|
||||
<mat-slide-toggle
|
||||
class="example-margin"
|
||||
[checked]="element[column.key]"
|
||||
[disabled]="true">
|
||||
|
||||
<mat-list role="list">
|
||||
@for (tutorial of tutorials; track tutorial) {
|
||||
<!--
|
||||
<mat-list-item role="listitem">
|
||||
{{tutorial.title}}
|
||||
</mat-slide-toggle>
|
||||
}
|
||||
@case ('datetime') {
|
||||
{{ element[column.key] | date: 'medium' }}
|
||||
}
|
||||
@default {
|
||||
{{ element[column.key] }}
|
||||
}
|
||||
}
|
||||
<!-- }-->
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<span class="spacer"></span>
|
||||
<button mat-button>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-button>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</mat-list-item>-->
|
||||
<mat-option >
|
||||
<div style="display:flex; justify-content: space-between">
|
||||
<span>{{tutorial.title}}</span>
|
||||
<span></span>
|
||||
<span (click)="deleteOption($event, tutorial)">X</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
}
|
||||
<!--<ng-container [matColumnDef]="col.key" *ngFor="let col of columnsSchema">-->
|
||||
<!--<th mat-header-cell *matHeaderCellDef [ngSwitch]="col.key">
|
||||
<span *ngSwitchCase="'isSelected'">
|
||||
<mat-checkbox
|
||||
(change)="selectAll($event)"
|
||||
[checked]="isAllSelected()"
|
||||
[indeterminate]="!isAllSelected() && isAnySelected()"
|
||||
></mat-checkbox>
|
||||
</span>
|
||||
<span *ngSwitchDefault>{{ col.label }}</span>
|
||||
</th>-->
|
||||
<!-- <mat-form-field-->
|
||||
<!--<td mat-cell *matCellDef="let element">
|
||||
<div [ngSwitch]="col.type" *ngIf="!element.isEdit">
|
||||
<ng-container *ngSwitchCase="'isSelected'">
|
||||
<mat-checkbox
|
||||
(change)="element.isSelected = $event.checked"
|
||||
[checked]="element.isSelected"
|
||||
></mat-checkbox>
|
||||
</ng-container>
|
||||
<div class="btn-edit" *ngSwitchCase="'isEdit'">
|
||||
<button mat-button (click)="element.isEdit = !element.isEdit">
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
class="button-remove"
|
||||
(click)="removeRow(element.id)"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
<span *ngSwitchCase="'date'">
|
||||
{{ element[col.key] | date: 'mediumDate' }}
|
||||
</span>
|
||||
<span *ngSwitchDefault>
|
||||
{{ element[col.key] }}
|
||||
</span>
|
||||
</div>
|
||||
<div [ngSwitch]="col.type" *ngIf="element.isEdit">
|
||||
<div *ngSwitchCase="'isSelected'"></div>
|
||||
<div class="btn-edit" *ngSwitchCase="'isEdit'">
|
||||
<button
|
||||
mat-button
|
||||
(click)="editRow(element)"
|
||||
[disabled]="disableSubmit(element.id)"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
class="form-input"
|
||||
*ngSwitchCase="'date'"
|
||||
appearance="fill"
|
||||
>
|
||||
<mat-label>Choose a date</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="picker"
|
||||
[(ngModel)]="element[col.key]"
|
||||
/>
|
||||
<mat-datepicker-toggle
|
||||
matSuffix
|
||||
[for]="picker"
|
||||
></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>-->
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
|
||||
|
||||
</mat-list>
|
||||
|
||||
+11
@@ -1,3 +1,14 @@
|
||||
.table-header {
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
text-align: right;
|
||||
margin-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.spacer{
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
+130
-26
@@ -1,6 +1,6 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit} from '@angular/core';
|
||||
import {MatList, MatListItem} from '@angular/material/list';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {Tutorial, TutorialColumns} from '../../models/tutorial.model';
|
||||
import {TutorialService} from '../../services/tutorial.service';
|
||||
import {UserApiService} from '../user-api.service';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
@@ -11,29 +11,80 @@ import {EventBusService} from '../../_shared/event-bus.service';
|
||||
import {EventData} from '../../_shared/event.class';
|
||||
import {Router, RouterLink} from '@angular/router';
|
||||
import {AuthService} from '../../services/auth.service';
|
||||
import {FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {
|
||||
MatCell,
|
||||
MatCellDef,
|
||||
MatColumnDef, MatHeaderCell, MatHeaderCellDef,
|
||||
MatHeaderRow,
|
||||
MatHeaderRowDef,
|
||||
MatRow,
|
||||
MatRowDef,
|
||||
MatTable,
|
||||
MatTableDataSource
|
||||
} from '@angular/material/table';
|
||||
import {MatFormField, MatInput} from '@angular/material/input';
|
||||
import {DatePipe, NgForOf, NgIf, NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';
|
||||
import {MatCheckbox} from '@angular/material/checkbox';
|
||||
import {MatDatepicker, MatDatepickerInput, MatDatepickerToggle} from '@angular/material/datepicker';
|
||||
import {MatSlideToggle} from '@angular/material/slide-toggle';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorials-list.component',
|
||||
imports: [
|
||||
MatList,
|
||||
MatListItem,
|
||||
MatLine,
|
||||
MatButton,
|
||||
MatIcon,
|
||||
MatOption,
|
||||
RouterLink
|
||||
],
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA],
|
||||
templateUrl: './tutorials-list.component.html',
|
||||
styleUrl: './tutorials-list.component.scss'
|
||||
})
|
||||
export class TutorialsListComponent implements OnInit {
|
||||
tutorials?: Tutorial[];
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorials-list.component',
|
||||
imports: [
|
||||
MatList,
|
||||
MatListItem,
|
||||
MatLine,
|
||||
MatButton,
|
||||
MatIcon,
|
||||
MatOption,
|
||||
RouterLink,
|
||||
ReactiveFormsModule,
|
||||
MatTable,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
FormsModule,
|
||||
MatInput,
|
||||
MatFormField,
|
||||
MatColumnDef,
|
||||
MatHeaderCell,
|
||||
MatHeaderCellDef,
|
||||
NgSwitch,
|
||||
NgSwitchCase,
|
||||
NgSwitchDefault,
|
||||
MatCellDef,
|
||||
MatCheckbox,
|
||||
MatDatepickerInput,
|
||||
MatDatepickerToggle,
|
||||
MatDatepicker,
|
||||
DatePipe,
|
||||
NgIf,
|
||||
NgForOf,
|
||||
MatCell,
|
||||
MatSlideToggle
|
||||
],
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA],
|
||||
templateUrl: './tutorials-list.component.html',
|
||||
styleUrl: './tutorials-list.component.scss'
|
||||
})
|
||||
export class TutorialsListComponent implements OnInit {
|
||||
//tutorials?: Tutorial[];
|
||||
|
||||
displayedColumns: string[] = TutorialColumns.map((col) => col.key)
|
||||
columnsSchema: any = TutorialColumns
|
||||
dataSource = new MatTableDataSource<Tutorial>()
|
||||
valid: any = {}
|
||||
|
||||
constructor(private userApiService: UserApiService,
|
||||
private storageService: TokenStorageService,
|
||||
private eventBusService: EventBusService,
|
||||
private router: Router, private authService: AuthService) {
|
||||
private router: Router,
|
||||
private authService: AuthService
|
||||
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
@@ -41,14 +92,13 @@ export class TutorialsListComponent implements OnInit {
|
||||
this.eventBusService.on('logout', () => {
|
||||
this.logout();
|
||||
})
|
||||
this.retrieveTutorials();
|
||||
this.getTutorials();
|
||||
}
|
||||
|
||||
retrieveTutorials(): void {
|
||||
getTutorials(): void {
|
||||
this.userApiService.getUserAllTutorials()
|
||||
.subscribe(
|
||||
data => {
|
||||
this.tutorials = data;
|
||||
.subscribe(( data:Tutorial[] ) => {
|
||||
this.dataSource.data = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
@@ -65,9 +115,63 @@ export class TutorialsListComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
deleteOption($event: MouseEvent, option: any) {
|
||||
selectAll(event: any) {
|
||||
this.dataSource.data = this.dataSource.data.map((item) => ({
|
||||
...item,
|
||||
isSelected: event.checked
|
||||
}));
|
||||
}
|
||||
|
||||
isAllSelected() {
|
||||
return this.dataSource.data.every((item) => item.isSelected)
|
||||
}
|
||||
|
||||
isAnySelected() {
|
||||
return this.dataSource.data.some((item) => item.isSelected)
|
||||
}
|
||||
|
||||
removeSelectedRows() {
|
||||
const selectedTutorials = this.dataSource.data.filter((u: Tutorial) => u.isSelected)
|
||||
/*this.dialog
|
||||
.open(ConfirmDialogComponent)
|
||||
.afterClosed()
|
||||
.subscribe((confirm) => {*/
|
||||
// if (confirm) {
|
||||
this.userApiService.deleteTutorials(selectedTutorials).subscribe(() => {
|
||||
this.dataSource.data = this.dataSource.data.filter(
|
||||
(u: Tutorial) => !u.isSelected
|
||||
)
|
||||
})
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
removeRow(id: number) {
|
||||
this.userApiService.deleteTutorial(id).subscribe(() => {
|
||||
this.dataSource.data = this.dataSource.data.filter(
|
||||
(u: Tutorial) => u.id !== id,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
editRow(row: Tutorial) {
|
||||
/*if (row.id === 0) {
|
||||
this.userService.addUser(row).subscribe((newUser: User) => {
|
||||
row.id = newUser.id
|
||||
row.isEdit = false
|
||||
})
|
||||
} else {
|
||||
this.userService.updateUser(row).subscribe(() => (row.isEdit = false))
|
||||
}*/
|
||||
}
|
||||
|
||||
disableSubmit(id: number) {
|
||||
if (this.valid[id]) {
|
||||
return Object.values(this.valid[id]).some((item) => item === false)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
logout(): void {
|
||||
this.authService.logout().subscribe({
|
||||
next: res => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {Observable} from 'rxjs';
|
||||
import {forkJoin, Observable} from 'rxjs';
|
||||
import {Tutorial} from '../models/tutorial.model';
|
||||
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
||||
|
||||
@@ -21,7 +21,28 @@ export class UserApiService {
|
||||
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`, this.httpOptions);
|
||||
}
|
||||
|
||||
getTutorial(id: string | null | undefined): Observable<Tutorial> {
|
||||
return this.http.get<Tutorial>(`${this.baseUrl}/tutorial-get/${id}`);
|
||||
}
|
||||
|
||||
create(data: any): Observable<any> {
|
||||
return this.http.post(`${this.baseUrl}/tutorial-add`, data);
|
||||
}
|
||||
|
||||
update(id: any,data: any): Observable<any> {
|
||||
return this.http.put(`${this.baseUrl}/tutorial-update/${id}`, data);
|
||||
}
|
||||
|
||||
deleteTutorials(tutorials: Tutorial[]): Observable<Tutorial[]> {
|
||||
return forkJoin(
|
||||
tutorials.map((tutorial) =>
|
||||
this.http.delete<Tutorial>(`${this.baseUrl}/tutorials/${tutorial.id}`)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
deleteTutorial(id: number): Observable<Tutorial> {
|
||||
return this.http.delete<Tutorial>(`${this.baseUrl}/tutorials/${id}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,4 +47,6 @@ mat-sidenav-content{
|
||||
}
|
||||
.pc-container{
|
||||
top: 0px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ const USER_ROUTES: Routes = [
|
||||
path: 'tutorial-add',
|
||||
loadComponent: () => import('../user-module/tutorial-add.component/tutorial-add.component').then((c) => c.TutorialAddComponent)
|
||||
},
|
||||
{
|
||||
path: 'tutorial-edit',
|
||||
loadComponent: () => import('../user-module/tutorial-edit.component/tutorial-edit.component').then((c) => c.TutorialEditComponent)
|
||||
},
|
||||
{
|
||||
path: 'ai-models',
|
||||
loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent)
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.jambotronGroup.jambotron.ZhiPuAi;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.image.*;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiImageModel;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
|
||||
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MyAIImageModel implements ImageModel {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ZhiPuAiImageModel.class);
|
||||
|
||||
public final RetryTemplate retryTemplate;
|
||||
|
||||
private final MyImageOptions defaultOptions;
|
||||
|
||||
private final ZhiPuAiImageApi zhiPuAiImageApi;
|
||||
|
||||
public MyAIImageModel(ZhiPuAiImageApi zhiPuAiImageApi) {
|
||||
this(zhiPuAiImageApi, MyImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE);
|
||||
}
|
||||
|
||||
public MyAIImageModel(ZhiPuAiImageApi zhiPuAiImageApi, MyImageOptions defaultOptions,
|
||||
RetryTemplate retryTemplate) {
|
||||
Assert.notNull(zhiPuAiImageApi, "ZhiPuAiImageApi must not be null");
|
||||
Assert.notNull(defaultOptions, "defaultOptions must not be null");
|
||||
Assert.notNull(retryTemplate, "retryTemplate must not be null");
|
||||
this.zhiPuAiImageApi = zhiPuAiImageApi;
|
||||
this.defaultOptions = defaultOptions;
|
||||
this.retryTemplate = retryTemplate;
|
||||
}
|
||||
|
||||
public MyImageOptions getDefaultOptions() {
|
||||
return this.defaultOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageResponse call(ImagePrompt imagePrompt) {
|
||||
return this.retryTemplate.execute(ctx -> {
|
||||
|
||||
String instructions = imagePrompt.getInstructions().get(0).getText();
|
||||
|
||||
ZhiPuAiImageApi.ZhiPuAiImageRequest imageRequest = new ZhiPuAiImageApi.ZhiPuAiImageRequest(instructions,
|
||||
ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL);
|
||||
|
||||
if (this.defaultOptions != null) {
|
||||
imageRequest = ModelOptionsUtils.merge(this.defaultOptions, imageRequest,
|
||||
ZhiPuAiImageApi.ZhiPuAiImageRequest.class);
|
||||
}
|
||||
|
||||
if (imagePrompt.getOptions() != null) {
|
||||
imageRequest = ModelOptionsUtils.merge(toZhiPuAiImageOptions(imagePrompt.getOptions()), imageRequest,
|
||||
ZhiPuAiImageApi.ZhiPuAiImageRequest.class);
|
||||
}
|
||||
|
||||
// Make the request
|
||||
ResponseEntity<ZhiPuAiImageApi.ZhiPuAiImageResponse> imageResponseEntity = this.zhiPuAiImageApi
|
||||
.createImage(imageRequest);
|
||||
|
||||
// Convert to org.springframework.ai.model derived ImageResponse data type
|
||||
return convertResponse(imageResponseEntity, imageRequest);
|
||||
});
|
||||
}
|
||||
|
||||
private ImageResponse convertResponse(ResponseEntity<ZhiPuAiImageApi.ZhiPuAiImageResponse> imageResponseEntity,
|
||||
ZhiPuAiImageApi.ZhiPuAiImageRequest zhiPuAiImageRequest) {
|
||||
ZhiPuAiImageApi.ZhiPuAiImageResponse imageApiResponse = imageResponseEntity.getBody();
|
||||
if (imageApiResponse == null) {
|
||||
logger.warn("No image response returned for request: {}", zhiPuAiImageRequest);
|
||||
return new ImageResponse(List.of());
|
||||
}
|
||||
|
||||
List<ImageGeneration> imageGenerationList = imageApiResponse.data()
|
||||
.stream()
|
||||
.map(entry -> new ImageGeneration(new Image(entry.url(), null)))
|
||||
.toList();
|
||||
|
||||
return new ImageResponse(imageGenerationList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the {@link ImageOptions} into {@link ZhiPuAiImageOptions}.
|
||||
* @param runtimeImageOptions the image options to use.
|
||||
* @return the converted {@link ZhiPuAiImageOptions}.
|
||||
*/
|
||||
private MyImageOptions toZhiPuAiImageOptions(ImageOptions runtimeImageOptions) {
|
||||
MyImageOptions.Builder myImageOptionsBuilder = MyImageOptions.builder();
|
||||
if (runtimeImageOptions != null) {
|
||||
if (runtimeImageOptions.getModel() != null) {
|
||||
myImageOptionsBuilder.model(runtimeImageOptions.getModel());
|
||||
}
|
||||
if (runtimeImageOptions instanceof MyImageOptions myImageOptions) {
|
||||
if (myImageOptions.getUser() != null) {
|
||||
myImageOptionsBuilder.user(myImageOptions.getUser());
|
||||
}
|
||||
}
|
||||
}
|
||||
return myImageOptionsBuilder.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.jambotronGroup.jambotron.ZhiPuAi;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.ai.image.ImageOptions;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
|
||||
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class MyImageOptions implements ImageOptions {
|
||||
/**
|
||||
* The model to use for image generation.
|
||||
*/
|
||||
@JsonProperty("model")
|
||||
private String model = ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL;
|
||||
|
||||
/**
|
||||
* A unique identifier representing your end-user, which can help ZhiPuAI to monitor
|
||||
* and detect abuse. User ID length requirement: minimum of 6 characters, maximum of
|
||||
* 128 characters
|
||||
*/
|
||||
@JsonProperty("user_id")
|
||||
private String user;
|
||||
|
||||
public static MyImageOptions.Builder builder() {
|
||||
|
||||
return new MyImageOptions.Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Integer getN() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Integer getWidth() {
|
||||
return 300;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Integer getHeight() {
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getResponseFormat() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getStyle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
// if (!(o instanceof ZhiPuAiImageOptions that)) {
|
||||
// return false;
|
||||
// }
|
||||
// return Objects.equals(this.model, that.model) && Objects.equals(this.user, that.user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.model, this.user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ZhiPuAiImageOptions{model='" + this.model + '\'' + ", user='" + this.user + '\'' + '}';
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private final MyImageOptions options;
|
||||
|
||||
Builder() {
|
||||
this.options = new MyImageOptions();
|
||||
}
|
||||
|
||||
public MyImageOptions.Builder model(String model) {
|
||||
this.options.setModel(model);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MyImageOptions.Builder user(String user) {
|
||||
this.options.setUser(user);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MyImageOptions build() {
|
||||
|
||||
return this.options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.jambotronGroup.jambotron.ZhiPuAi;
|
||||
|
||||
|
||||
import org.springframework.ai.image.ImageOptions;
|
||||
import org.springframework.ai.image.ImageOptionsBuilder;
|
||||
import org.springframework.ai.image.ImagePrompt;
|
||||
import org.springframework.ai.image.ImageResponse;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiImageModel;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
|
||||
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -21,18 +25,48 @@ public class ZhiPuAiImageService {
|
||||
|
||||
private ZhiPuAiImageApi _zhiPuAiImageApi;
|
||||
|
||||
private MyAIImageModel _myAIImageModel;
|
||||
|
||||
private ZhiPuAiImageModel _zhiPuAiImageModel;
|
||||
|
||||
private MyImageOptions _myImageOptions;
|
||||
|
||||
public ZhiPuAiImageService() {
|
||||
|
||||
_zhiPuAiImageApi = new ZhiPuAiImageApi("628447c8c65845a48a7226391464a2ea.Dw8ci6TiW0BRF5LI");
|
||||
|
||||
ImageOptionsBuilder imageOptionsBuilder = ImageOptionsBuilder.builder();
|
||||
|
||||
ImageOptions imageOptions = imageOptionsBuilder.model("Cogview-3-Flash")//ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL)
|
||||
.height(100)
|
||||
.width(200)
|
||||
|
||||
.build();
|
||||
// _myImageOptions = new MyImageOptions.Builder()
|
||||
// .model(ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL)
|
||||
// .user("jambotron")
|
||||
//
|
||||
// .build();
|
||||
|
||||
|
||||
_zhiPuAiImageModel = new ZhiPuAiImageModel(_zhiPuAiImageApi);
|
||||
_myAIImageModel = new MyAIImageModel(_zhiPuAiImageApi);
|
||||
|
||||
}
|
||||
|
||||
public ImageResponse generateImage(String prompt) {
|
||||
ImageOptionsBuilder imageOptionsBuilder = ImageOptionsBuilder.builder();
|
||||
ImageOptions imageOptions = imageOptionsBuilder.model("Cogview-3-Flash")//ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL)
|
||||
.height(1440)
|
||||
.width(720)
|
||||
|
||||
.build();
|
||||
// Create an ImagePrompt object with the desired prompt
|
||||
ImagePrompt imagePrompt = new ImagePrompt(prompt);
|
||||
ImagePrompt imagePrompt = new ImagePrompt(prompt,imageOptions);
|
||||
|
||||
/*// Call the generate method to get the image response
|
||||
ImageResponse imageResponse = _myAIImageModel.call(imagePrompt);
|
||||
*/
|
||||
|
||||
// Call the generate method to get the image response
|
||||
ImageResponse imageResponse = _zhiPuAiImageModel.call(imagePrompt);
|
||||
|
||||
@@ -7,21 +7,17 @@ import com.jambotronGroup.jambotron.repository.TutorialRepository;
|
||||
import com.jambotronGroup.jambotron.repository.UserRepository;
|
||||
import com.jambotronGroup.jambotron.security.AuthenticationFacade;
|
||||
import com.jambotronGroup.jambotron.security.services.UserDetailsImpl;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
//@CrossOrigin(origins = "http://localhost:4200", maxAge = 3600, allowCredentials="true")
|
||||
@@ -106,18 +102,7 @@ public class TutorialController {
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/tutorials/{id}")
|
||||
public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
|
||||
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
|
||||
|
||||
if (tutorialData.isPresent()) {
|
||||
return new ResponseEntity<>(tutorialData.get(), HttpStatus.OK);
|
||||
} else {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("user/tutorial-add")
|
||||
@PostMapping("user/tutorial-add")
|
||||
public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
|
||||
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
@@ -127,29 +112,49 @@ public class TutorialController {
|
||||
|
||||
try {
|
||||
Tutorial _tutorial = tutorialRepository
|
||||
.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false, user));
|
||||
.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false, user, Timestamp.valueOf(LocalDateTime.now()), Timestamp.valueOf(LocalDateTime.now())));
|
||||
return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
|
||||
} catch (Exception e) {
|
||||
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/tutorials/{id}")
|
||||
public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
|
||||
@GetMapping("user/tutorial-get/{id}")
|
||||
public ResponseEntity<Tutorial> getTutorial(@PathVariable("id") long id) {
|
||||
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
|
||||
|
||||
if (tutorialData.isPresent()) {
|
||||
Tutorial _tutorial = tutorialData.get();
|
||||
_tutorial.setTitle(tutorial.getTitle());
|
||||
_tutorial.setDescription(tutorial.getDescription());
|
||||
_tutorial.setPublished(tutorial.isPublished());
|
||||
return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
|
||||
|
||||
return new ResponseEntity<Tutorial>(tutorialData.get(), HttpStatus.OK);
|
||||
} else {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/tutorials/{id}")
|
||||
@PutMapping("user/tutorial-update/{id}")
|
||||
public ResponseEntity<?> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
|
||||
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
|
||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
if (tutorialData.isPresent()) {
|
||||
Tutorial servTutorial = tutorialData.get();
|
||||
servTutorial.setTitle(tutorial.getTitle());
|
||||
servTutorial.setDescription(tutorial.getDescription());
|
||||
servTutorial.setPublished(tutorial.isPublished());
|
||||
try {
|
||||
servTutorial = tutorialRepository.save(servTutorial);
|
||||
} catch (Exception e) {
|
||||
|
||||
map.put("status", 0);
|
||||
map.put("message", e.getMessage());
|
||||
return new ResponseEntity<>(map,HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
return new ResponseEntity<>(servTutorial, HttpStatus.OK);
|
||||
} else {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("user/tutorials/{id}")
|
||||
public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
|
||||
try {
|
||||
tutorialRepository.deleteById(id);
|
||||
|
||||
@@ -25,15 +25,25 @@ public class Tutorial {
|
||||
@JoinColumn(name = "userID", nullable = false)
|
||||
private User user;
|
||||
|
||||
@Column(name = "created")
|
||||
private java.sql.Timestamp created;
|
||||
|
||||
@Column(name = "modified")
|
||||
private java.sql.Timestamp modified;
|
||||
|
||||
|
||||
|
||||
public Tutorial() {
|
||||
|
||||
}
|
||||
|
||||
public Tutorial(String title, String description, boolean published, User user) {
|
||||
public Tutorial(String title, String description, boolean published, User user, java.sql.Timestamp created, java.sql.Timestamp modified) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.published = published;
|
||||
this.user = user;
|
||||
this.created = created;
|
||||
this.modified = modified;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
@@ -64,6 +74,22 @@ public class Tutorial {
|
||||
this.published = isPublished;
|
||||
}
|
||||
|
||||
public void setCreated(java.sql.Timestamp created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public java.sql.Timestamp getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setModified(java.sql.Timestamp modified) {
|
||||
this.modified = modified;
|
||||
}
|
||||
|
||||
public java.sql.Timestamp getModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.jambotronGroup.jambotron.utils;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
public class DateTimeHelper {
|
||||
|
||||
public static SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public static java.sql.Timestamp parseTimestamp(String timestamp) {
|
||||
try {
|
||||
return new Timestamp(DATE_TIME_FORMAT.parse(timestamp).getTime());
|
||||
} catch (ParseException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,32 +2,32 @@ spring.application.name=jambotron
|
||||
|
||||
#============Localhost Configurations========================
|
||||
|
||||
#spring.datasource.url= jdbc:postgresql://localhost:5432/jambotronDB
|
||||
#spring.datasource.username= admin
|
||||
#spring.datasource.password= postgrespw
|
||||
|
||||
#spring.flyway.baseline-on-migrate=true
|
||||
#spring.flyway.validate-on-migrate=true
|
||||
#
|
||||
#spring.flyway.url=jdbc:postgresql://localhost:5432/jambotronDB
|
||||
#spring.flyway.user=admin
|
||||
#spring.flyway.password=postgrespw
|
||||
|
||||
|
||||
#============Koyeb Configurations========================
|
||||
|
||||
spring.datasource.url= jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app/jambotronDB
|
||||
spring.datasource.username= koyeb-adm
|
||||
spring.datasource.password= npg_HfFEUA7bay1i
|
||||
spring.datasource.url= jdbc:postgresql://localhost:5432/jambotronDB
|
||||
spring.datasource.username= admin
|
||||
spring.datasource.password= postgrespw
|
||||
|
||||
spring.flyway.baseline-on-migrate=true
|
||||
spring.flyway.validate-on-migrate=true
|
||||
|
||||
spring.flyway.url=jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app/jambotronDB
|
||||
spring.flyway.user=koyeb-adm
|
||||
spring.flyway.password=npg_HfFEUA7bay1i
|
||||
spring.flyway.url=jdbc:postgresql://localhost:5432/jambotronDB
|
||||
spring.flyway.user=admin
|
||||
spring.flyway.password=postgrespw
|
||||
|
||||
|
||||
#============Koyeb Configurations========================
|
||||
|
||||
#spring.datasource.url= jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app/jambotronDB
|
||||
#spring.datasource.username= koyeb-adm
|
||||
#spring.datasource.password= npg_HfFEUA7bay1i
|
||||
#
|
||||
#spring.flyway.baseline-on-migrate=true
|
||||
#spring.flyway.validate-on-migrate=true
|
||||
#
|
||||
#spring.flyway.url=jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app/jambotronDB
|
||||
#spring.flyway.user=koyeb-adm
|
||||
#spring.flyway.password=npg_HfFEUA7bay1i
|
||||
#
|
||||
|
||||
#spring.datasource.url=${SPRING_DATASOURCE_URL}
|
||||
#spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
|
||||
#spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
|
||||
|
||||
Reference in New Issue
Block a user