Skip to content

Commit 5042063

Browse files
committed
use Api consistently and update README to reflect latest updates
1 parent 088c37b commit 5042063

2 files changed

Lines changed: 121 additions & 67 deletions

File tree

README.md

Lines changed: 114 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -18,70 +18,105 @@ A custom [OpenAPI Generator](https://openapi-generator.tech/) for generating mod
1818

1919
### Models
2020
```typescript
21-
export interface User {
21+
export interface Course {
2222
readonly id: number;
23-
readonly login: string;
24-
readonly email?: string;
25-
readonly firstName?: string;
26-
readonly lastName?: string;
23+
readonly title: string;
24+
readonly shortName: string;
25+
readonly description?: string;
26+
readonly startDate?: string;
27+
readonly endDate?: string;
28+
readonly semester?: string;
29+
readonly testCourse?: boolean;
30+
readonly onlineCourse?: boolean;
31+
readonly maxComplaints?: number;
32+
readonly maxTeamComplaints?: number;
33+
readonly maxComplaintTimeDays?: number;
34+
readonly studentGroupName?: string;
35+
readonly teachingAssistantGroupName?: string;
36+
readonly editorGroupName?: string;
37+
readonly instructorGroupName?: string;
38+
readonly color?: string;
39+
readonly courseIcon?: string;
2740
}
2841

29-
export interface UserCreate {
30-
login: string;
31-
email: string;
32-
firstName?: string;
33-
lastName?: string;
42+
export interface CourseCreate {
43+
title: string;
44+
shortName: string;
45+
description?: string;
46+
startDate?: string;
47+
endDate?: string;
48+
semester?: string;
49+
testCourse?: boolean;
50+
onlineCourse?: boolean;
51+
color?: string;
52+
}
53+
54+
export interface CourseUpdate {
55+
title?: string;
56+
description?: string;
57+
startDate?: string;
58+
endDate?: string;
59+
semester?: string;
60+
color?: string;
3461
}
3562
```
3663

3764
### API Service (Mutations)
3865
```typescript
3966
@Injectable({ providedIn: 'root' })
40-
export class UserApi {
67+
export class CourseApi {
4168
private readonly http = inject(HttpClient);
69+
private readonly basePath = '/api';
4270

43-
createUser(body: UserCreate): Observable<User> {
44-
return this.http.post<User>(`/api/users`, body);
71+
createCourse(courseCreate: CourseCreate): Observable<Course> {
72+
const url = `${this.basePath}/courses`;
73+
return this.http.post<Course>(url, courseCreate);
4574
}
4675

47-
updateUser(userId: number, body: UserUpdate): Observable<User> {
48-
return this.http.put<User>(`/api/users/${userId}`, body);
76+
deleteCourse(courseId: number): Observable<void> {
77+
const url = `${this.basePath}/courses/$${courseId}`;
78+
return this.http.delete(url);
4979
}
5080

51-
deleteUser(userId: number): Observable<void> {
52-
return this.http.delete<void>(`/api/users/${userId}`);
81+
updateCourse(courseId: number, courseUpdate: CourseUpdate): Observable<Course> {
82+
const url = `${this.basePath}/courses/$${courseId}`;
83+
return this.http.put<Course>(url, courseUpdate);
5384
}
5485
}
5586
```
5687

5788
### Resources (GET with httpResource)
5889
```typescript
59-
export interface GetAllUsersParams {
60-
search?: string;
90+
const BASE_PATH = '/api';
91+
92+
export interface GetAllCoursesParams {
93+
onlyActive?: boolean;
6194
page?: number;
6295
size?: number;
6396
}
6497

65-
export function getAllUsersResource(
66-
params?: Signal<GetAllUsersParams>
67-
): HttpResourceRef<User[] | undefined> {
68-
return httpResource<User[]>(() => {
69-
const p = params?.() ?? {};
98+
export function getAllCoursesResource(params?: Signal<GetAllCoursesParams>): HttpResourceRef<Array<Course> | undefined> {
99+
return httpResource<Array<Course>>(() => {
100+
const queryParams = params?.() ?? {};
70101
const searchParams = new URLSearchParams();
71-
if (p.search) searchParams.set('search', p.search);
72-
if (p.page !== undefined) searchParams.set('page', String(p.page));
73-
if (p.size !== undefined) searchParams.set('size', String(p.size));
102+
if (queryParams.onlyActive !== undefined) {
103+
searchParams.set('onlyActive', String(queryParams.onlyActive));
104+
}
105+
if (queryParams.page !== undefined && queryParams.page !== null) {
106+
searchParams.set('page', String(queryParams.page));
107+
}
108+
if (queryParams.size !== undefined && queryParams.size !== null) {
109+
searchParams.set('size', String(queryParams.size));
110+
}
74111
const query = searchParams.toString();
75-
return `/api/users${query ? `?${query}` : ''}`;
112+
return `${BASE_PATH}/courses${query ? `?${query}` : ''}`;
76113
});
77114
}
78115

79-
export function getUserResource(
80-
userId: Signal<number> | number
81-
): HttpResourceRef<User | undefined> {
82-
return httpResource<User>(() => {
83-
const userIdValue = typeof userId === 'function' ? userId() : userId;
84-
return `/api/users/${userIdValue}`;
116+
export function getCourseResource(courseId: Signal<number> | number): HttpResourceRef<Course | undefined> {
117+
return httpResource<Course>(() => {
118+
const courseIdValue = typeof courseId === 'function' ? courseId() : courseId;
119+
return `${BASE_PATH}/courses/${courseIdValue}`;
85120
});
86121
}
87122
```
@@ -147,7 +182,7 @@ openApiGenerate {
147182
<plugin>
148183
<groupId>org.openapitools</groupId>
149184
<artifactId>openapi-generator-maven-plugin</artifactId>
150-
<version>7.10.0</version>
185+
<version>7.18.0</version>
151186
<executions>
152187
<execution>
153188
<goals>
@@ -183,7 +218,7 @@ openApiGenerate {
183218
wget https://github.com/ls1intum/openapi-generator-angular21/releases/download/v1.0.0/openapi-generator-angular21-1.0.0.jar
184219

185220
# Generate code
186-
java -cp openapi-generator-angular21-1.0.0.jar:openapi-generator-cli-7.10.0.jar \
221+
java -cp openapi-generator-angular21-1.0.0.jar:openapi-generator-cli-7.18.0.jar \
187222
org.openapitools.codegen.OpenAPIGenerator generate \
188223
-g angular21 \
189224
-i openapi.yaml \
@@ -192,73 +227,85 @@ java -cp openapi-generator-angular21-1.0.0.jar:openapi-generator-cli-7.10.0.jar
192227

193228
## Configuration Options
194229

195-
| Option | Default | Description |
196-
|--------|---------|-------------|
197-
| `useHttpResource` | `true` | Use `httpResource` for GET requests instead of `HttpClient` |
198-
| `useInjectFunction` | `true` | Use `inject()` function instead of constructor injection |
199-
| `separateResources` | `true` | Generate separate `*-resources.ts` files for GET operations |
200-
| `readonlyModels` | `true` | Add `readonly` modifier to response model properties |
230+
| Option | Default | Description |
231+
|---------------------|---------|-------------------------------------------------------------|
232+
| `useHttpResource` | `true` | Use `httpResource` for GET requests instead of `HttpClient` |
233+
| `useInjectFunction` | `true` | Use `inject()` function instead of constructor injection |
234+
| `separateResources` | `true` | Generate separate `*-resources.ts` files for GET operations |
235+
| `readonlyModels` | `true` | Add `readonly` modifier to response model properties |
201236

202237
## Usage in Components
203238

204239
```typescript
205240
import { Component, computed, signal, inject } from '@angular/core';
206241
import { firstValueFrom } from 'rxjs';
207242
import {
208-
UserApi,
209-
getUserResource,
210-
getAllUsersResource,
211-
User,
212-
UserCreate
243+
CourseApi,
244+
getCourseResource,
245+
getAllCoursesResource,
246+
Course,
247+
CourseCreate,
248+
CourseUpdate
213249
} from './generated';
214250

215251
@Component({
216-
selector: 'app-user-list',
252+
selector: 'app-course-list',
217253
standalone: true,
218254
template: `
219-
@if (users.isLoading()) {
255+
@if (courses.isLoading()) {
220256
<div class="spinner">Loading...</div>
221257
}
222258
223-
@if (users.hasValue()) {
224-
@for (user of users.value(); track user.id) {
225-
<div (click)="selectUser(user.id)">
226-
{{ user.firstName }} {{ user.lastName }}
259+
@if (courses.hasValue()) {
260+
@for (course of courses.value() ?? []; track course.id) {
261+
<div (click)="selectCourse(course.id)">
262+
{{ course.title }}
227263
</div>
228264
}
229265
}
230266
231-
@if (selectedUser.hasValue()) {
267+
@if (selectedCourse.hasValue()) {
232268
<div class="details">
233-
Selected: {{ selectedUser.value()?.email }}
269+
Selected: {{ selectedCourse.value()?.shortName }}
234270
</div>
235271
}
236272
`
237273
})
238-
export class UserListComponent {
239-
private readonly userApi = inject(UserApi);
274+
export class CourseListComponent {
275+
private readonly courseApi = inject(CourseApi);
240276

241277
// Reactive state
242278
protected readonly page = signal(0);
243-
protected readonly selectedUserId = signal<number | undefined>(undefined);
279+
protected readonly selectedCourseId = signal<number | undefined>(undefined);
244280

245281
// Resources - automatically refetch when signals change
246-
protected readonly users = getAllUsersResource(
247-
computed(() => ({ page: this.page(), size: 20 }))
282+
protected readonly courses = getAllCoursesResource(
283+
computed(() => ({ page: this.page(), size: 20, onlyActive: true }))
248284
);
249285

250-
protected readonly selectedUser = getUserResource(
251-
computed(() => this.selectedUserId() ?? -1)
286+
protected readonly selectedCourse = getCourseResource(
287+
computed(() => this.selectedCourseId() ?? -1)
252288
);
253289

254-
selectUser(id: number): void {
255-
this.selectedUserId.set(id);
290+
selectCourse(id: number): void {
291+
this.selectedCourseId.set(id);
256292
}
257293

258-
async createUser(data: UserCreate): Promise<void> {
259-
const created = await firstValueFrom(this.userApi.createUser(data));
294+
async createCourse(data: CourseCreate): Promise<void> {
295+
const created = await firstValueFrom(this.courseApi.createCourse(data));
260296
console.log('Created:', created);
261-
this.users.reload(); // Refresh the list
297+
this.courses.reload(); // Refresh the list
298+
}
299+
300+
async updateCourse(courseId: number, data: CourseUpdate): Promise<void> {
301+
const updated = await firstValueFrom(this.courseApi.updateCourse(courseId, data));
302+
console.log('Updated:', updated);
303+
this.courses.reload(); // Refresh the list
304+
}
305+
306+
async deleteCourse(courseId: number): Promise<void> {
307+
await firstValueFrom(this.courseApi.deleteCourse(courseId));
308+
this.courses.reload(); // Refresh the list
262309
}
263310
}
264311
```

src/main/java/de/tum/cit/aet/openapi/Angular21Generator.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.openapitools.codegen.model.ModelsMap;
1111
import org.openapitools.codegen.model.OperationMap;
1212
import org.openapitools.codegen.model.OperationsMap;
13+
import org.openapitools.codegen.utils.StringUtils;
1314
import org.slf4j.Logger;
1415
import org.slf4j.LoggerFactory;
1516

@@ -67,6 +68,7 @@ public Angular21Generator() {
6768
modelTemplateFiles.clear();
6869
modelTemplateFiles.put("model.mustache", ".ts");
6970

71+
apiNameSuffix = "Api";
7072
apiTemplateFiles.clear();
7173
apiTemplateFiles.put("api-service.mustache", "-api.ts");
7274

@@ -153,6 +155,11 @@ public String toApiFilename(String name) {
153155
return toKebabCase(name);
154156
}
155157

158+
@Override
159+
public String toApiName(String name) {
160+
return StringUtils.camelize(name) + "Api";
161+
}
162+
156163
@Override
157164
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
158165
Map<String, ModelsMap> result = super.postProcessAllModels(objs);

0 commit comments

Comments
 (0)